Main Content

FIR Halfband Filter Design

This example shows how to design FIR halfband filters. Halfband filters are widely used in multirate signal processing applications when interpolating or decimating by a factor of two. Halfband filters are implemented efficiently in polyphase form because approximately half of the halfband filter coefficients are equal to zero.

Halfband filters have two important characteristics:

  • The passband and stopband ripples must be the same.

  • The passband-edge and the stopband-edge frequencies are equidistant from the halfband frequency Fs4 (or π2 rad/sample in normalized frequency).

Obtaining the Halfband Coefficients

The firhalfband function returns the coefficients of an FIR halfband equiripple filter. As a simple example, consider a halfband filter dealing with data sampled at 96 kHz and has a passband frequency of 22 kHz.

Fs  = 96e3;
Fp  = 22e3;
N   = 100;
num = firhalfband(N,Fp/(Fs/2));
zerophase(num,1,linspace(0,Fs/2,512),Fs);

Figure contains an axes object. The axes object with title Zero-phase response contains an object of type line.

By zooming into the response, you can verify that the passband and stopband peak-to-peak ripples are the same. Also there is symmetry about the Fs4 (24 kHz) point. The passband extends up to 22 kHz as specified and the stopband begins at 26 kHz. We can also verify that every other coefficient is equal to zero by looking at the impulse response. This makes the filter very efficient to implement for interpolation or decimation by a factor of 2.

fvt = fvtool(num,Fs=Fs);
fvt.Analysis = "impulse";

{"String":"Figure Figure 1: Impulse Response contains an axes object. The axes object with title Impulse Response contains an object of type stem.","Tex":"Impulse Response","LaTex":[]}

dsp.FIRHalfbandInterpolator and dsp.FIRHalfbandDecimator

The firhalfband function provides several other design options. However, using dsp.FIRHalfbandInterpolator and dsp.FIRHalfbandDecimator System objects is recommended when working with streaming data. These two System objects not only design the coefficients, but also provide efficient polyphase implementation. They support filtering double, single precision floating-point data as well as fixed-point data. They also support C and HDL code generation as well as optimized ARM® Cortex® M and ARM® Cortex® A code generation.

halfbandInterpolator = dsp.FIRHalfbandInterpolator(SampleRate=Fs,...
    Specification="Filter order and transition width",...
    FilterOrder=N,TransitionWidth=4000);
fvt = fvtool(halfbandInterpolator,Fs=2*Fs); %#ok<NASGU> 

{"String":"Figure Figure 2: Magnitude Response (dB) contains an axes object. The axes object with title Magnitude Response (dB) contains 2 objects of type line.","Tex":"Magnitude Response (dB)","LaTex":[]}

In order to perform the interpolation, use the dsp.FIRHalfbandInterpolator System object™. Because this is a multirate filter, it is important to define what is meant by the sample rate. For this and all other System objects, the sample rate refers to the sample rate of the input signal. However, FVTool defines the sample rate as the rate at which the filter is running. In the case of interpolation, you upsample and then filter (conceptually), therefore the sample rate of FVTool needs to be specified as 2Fs because of the upsampling by 2.

FrameSize = 256;
scope = spectrumAnalyzer(SampleRate=2*Fs);
sine1 = dsp.SineWave(Frequency=10e3,SampleRate=Fs,...
    SamplesPerFrame=FrameSize);
sine2 = dsp.SineWave(Frequency=20e3,SampleRate=Fs,...
    SamplesPerFrame=FrameSize);
tic
while toc < 10
    x = sine1() + sine2() + 0.01.*randn(FrameSize,1); %  96 kHz
    y = halfbandInterpolator(x);                      % 192 kHz
    scope(y);
end

release(scope);

Notice that the spectral replicas are attenuated by about 40 dB which is roughly the attenuation provided by the halfband filter. You can obtain a plot with the interpolated samples overlaid on the input samples by compensating for the group-delay of the filter. Notice that the input samples remain unchanged at the output of the filter. This is because one of the polyphase branches of the halfband filter is a pure delay branch which does not change the input samples.

grpDel = 50;
n = 0:2:511;
stem(n(1:end-grpDel/2),x(1:end-grpDel/2),"k","filled")
hold on
nu = 0:511;
stem(nu(1:end-grpDel),y(grpDel+1:end))
legend("Input samples","Interpolated samples")

Figure contains an axes object. The axes object contains 2 objects of type stem. These objects represent Input samples, Interpolated samples.

In the case of decimation, the sample rate specified in dsp.FIRHalfbandDecimator corresponds to the sample rate of the filter, since the object filters and then downsamples (conceptually). So for decimators, theFs specified in FVTool does not need to be multiplied by any factor.

FrameSize = 256;
FsIn = 2*Fs;
halfbandDecimator = dsp.FIRHalfbandDecimator(SampleRate=FsIn,...
    Specification="Filter order and transition width",...
    FilterOrder=N,TransitionWidth=4000);
fvt = fvtool(halfbandDecimator,Fs=FsIn);%#ok<NASGU> 

{"String":"Figure Figure 3: Magnitude Response (dB) contains an axes object. The axes object with title Magnitude Response (dB) contains 2 objects of type line.","Tex":"Magnitude Response (dB)","LaTex":[]}

scope = spectrumAnalyzer(SampleRate=Fs);
sine1 = dsp.SineWave(Frequency=10e3,SampleRate=Fs,...
    SamplesPerFrame=FrameSize);
sine2 = dsp.SineWave(Frequency=20e3,SampleRate=Fs,...
    SamplesPerFrame=FrameSize);
tic
while toc < 10
    x = sine1() + sine2() + 0.01.*randn(FrameSize,1); %  96 kHz
    y = halfbandInterpolator(x);                      % 192 kHz
    xd = halfbandDecimator(y);                        %  96 kHz
    scope(xd);
end

release(scope);

Obtaining the Filter Coefficients

The filter coefficients can be extracted from the interpolator/decimator by using the tf function.

num = tf(halfbandInterpolator); % Or num = tf(halfbandDecimator);

Using Different Design Specifications

Instead of specifying the filter order and transition width, you can design a minimum-order filter that provides a given transition width as well as a given stopband attenuation.

Ast = 80; % 80 dB
halfbandInterpolator = dsp.FIRHalfbandInterpolator(SampleRate=Fs,...
    Specification="Transition width and stopband attenuation",...
    StopbandAttenuation=Ast,TransitionWidth=4000);
fvtool(halfbandInterpolator,Fs=2*Fs);

{"String":"Figure Figure 4: Magnitude Response (dB) contains an axes object. The axes object with title Magnitude Response (dB) contains 2 objects of type line.","Tex":"Magnitude Response (dB)","LaTex":[]}

Notice that as with all interpolators, the passband gain in absolute units is equal to the interpolation factor (2 in the case of halfbands). This corresponds to a passband gain of 6.02 dB.

It is also possible to specify the filter order and the stopband attenuation.

halfbandDecimator = dsp.FIRHalfbandDecimator(SampleRate=Fs,...
    Specification="Filter order and stopband attenuation",...
    StopbandAttenuation=Ast,FilterOrder=N);
fvtool(halfbandDecimator,Fs=Fs);

{"String":"Figure Figure 5: Magnitude Response (dB) contains an axes object. The axes object with title Magnitude Response (dB) contains 2 objects of type line.","Tex":"Magnitude Response (dB)","LaTex":[]}

Unlike interpolators, decimators have a gain of 1 (0 dB) in the passband.

Using Halfband Filters for Filter Banks

Halfband interpolators and decimators can be used to efficiently implement synthesis/analysis filter banks. The halfband filters shown so far have all been lowpass filters. With a single extra adder, it is possible to obtain a highpass response in addition to the lowpass response and use the two responses for the filter bank implementation.

The following code simulates a quadrature mirror filter (QMF) bank. An 8 kHz signal consisting of 1 kHz and 3 kHz sine waves is separated into two 4 kHz signals using a lowpass/highpass halfband decimator. The lowpass signal retains the 1 kHz sine wave while the highpass signal retains the 3 kHz sine wave (which is aliased to 1 kHz after downsampling). The signals are then merged back together with a synthesis filter bank using a halfband interpolator. The highpass branch upconverts the aliased 1 kHz sine wave back to 3 kHz. The interpolated signal has an 8 kHz sample rate.

Fs1 = 8000; % Units = Hz
Spec = "Filter order and transition width";
Order = 52;
TW = 4.1e2; % Units = Hz

% Construct FIR Halfband Interpolator
halfbandInterpolator = dsp.FIRHalfbandInterpolator( ...
    Specification=Spec,...
    FilterOrder=Order,...
    TransitionWidth=TW,...
    SampleRate=Fs1/2,...
    FilterBankInputPort=true);

% Construct FIR Halfband Decimator
halfbandDecimator = dsp.FIRHalfbandDecimator( ...
    Specification=Spec,...
    FilterOrder=Order,...
    TransitionWidth=TW,...
    SampleRate=Fs1);

% Input
f1 = 1000;
f2 = 3000;
InputWave = dsp.SineWave(Frequency=[f1,f2],SampleRate=Fs1,...
    SamplesPerFrame=1024,Amplitude=[1 0.25]);

% Construct Spectrum Analyzer object to view the input and output
scope = spectrumAnalyzer(SampleRate=Fs1,...
    PlotAsTwoSidedSpectrum=false,ShowLegend=true,...
    YLimits=[-120 30],...
    Title="Input Signal and Output Signal of Quadrature Mirror Filter",...
    ChannelNames={"Input","Output"}); %#ok<CLARRSTR> 

tic
while toc < 10
    Input = sum(InputWave(),2);
    NoisyInput = Input+(10^-5)*randn(1024,1);
    [Lowpass,Highpass] = halfbandDecimator(NoisyInput);
    Output = halfbandInterpolator(Lowpass,Highpass);
    scope([NoisyInput,Output]);
end

release(scope);

Advanced Design Options: Specifying Different Design Algorithms

All designs presented so far have been optimal equiripple designs. dsp.FIRHalfbandDecimator and dsp.FIRHalfbandInterpolator System objects can also design their filters using the Kaiser window method.

Fs = 44.1e3;
N  = 90;
TW = 1000;
equirippleHBFilter = dsp.FIRHalfbandInterpolator(DesignMethod="Equiripple",...
    Specification="Filter order and transition width",...
    SampleRate=Fs,...
    TransitionWidth=TW,...
    FilterOrder=N); 
kaiserHBFilter = dsp.FIRHalfbandInterpolator(DesignMethod="Kaiser",...
    Specification="Filter order and transition width",...
    SampleRate=Fs,...
    TransitionWidth=TW,...
    FilterOrder=N); 

You can compare the designs with FVTool. The two designs allow for tradeoffs between minimum stopband attenuation and larger overall attenuation.

fvt = fvtool(equirippleHBFilter,kaiserHBFilter,Fs=2*Fs);
legend(fvt,"Equiripple design","Kaiser-window design")

{"String":"Figure Figure 6: Magnitude Response (dB) contains an axes object. The axes object with title Magnitude Response (dB) contains 3 objects of type line. These objects represent Equiripple design, Kaiser-window design.","Tex":"Magnitude Response (dB)","LaTex":[]}

If you use thefdesign.interpolator and fdesign.decimator objects, other design algorithms, such as Least-square linear-filter FIR filter design are available. To determine the list of available design methods for a given filter specification object, use the designmethods function.

filtSpecs = fdesign.interpolator(2,"halfband","N,TW",N,TW/Fs);
designmethods(filtSpecs,"FIR");
FIR Design Methods for class fdesign.interpolator (N,TW):


equiripple
firls
kaiserwin

Controlling the Stopband Attenuation

Alternatively, one can specify the order and the stopband attenuation. This allows for tradeoffs between overall stopband attenuation and transition width.

Ast  = 60; % Minimum stopband attenuation
equirippleHBFilter = dsp.FIRHalfbandInterpolator(DesignMethod="Equiripple",...
    Specification="Filter order and stopband attenuation",...
    SampleRate=Fs,...
    StopbandAttenuation=Ast,...
    FilterOrder=N); 
kaiserHBFilter = dsp.FIRHalfbandInterpolator(DesignMethod="Kaiser",...
    Specification="Filter order and stopband attenuation",...
    SampleRate=Fs,...
    StopbandAttenuation=Ast,...
    FilterOrder=N); 
fvt = fvtool(equirippleHBFilter,kaiserHBFilter,Fs=2*Fs);
legend(fvt,"Equiripple design","Kaiser-window design")

{"String":"Figure Figure 7: Magnitude Response (dB) contains an axes object. The axes object with title Magnitude Response (dB) contains 3 objects of type line. These objects represent Equiripple design, Kaiser-window design.","Tex":"Magnitude Response (dB)","LaTex":[]}

Minimum-Order Designs

Kaiser window designs can also be used in addition to equiripple designs when designing a filter of the minimum-order necessary to meet the design specifications. The actual order for the Kaiser window design is larger than that needed for the equiripple design, but the overall stopband attenuation is better in return.

Fs = 44.1e3;
TW = 1000; % Transition width
Ast = 60;  % 60 dB minimum attenuation in the stopband

equirippleHBFilter = dsp.FIRHalfbandDecimator(DesignMethod="Equiripple",...
    Specification="Transition width and stopband attenuation",...
    SampleRate=Fs,...
    TransitionWidth=TW,...
    StopbandAttenuation=Ast); 
kaiserHBFilter = dsp.FIRHalfbandDecimator(DesignMethod="Kaiser",...
    Specification="Transition width and stopband attenuation",...
    SampleRate=Fs,...
    TransitionWidth=TW,...
    StopbandAttenuation=Ast); 
fvt = fvtool(equirippleHBFilter,kaiserHBFilter);
legend(fvt,"Equiripple design","Kaiser-window design")

{"String":"Figure Figure 8: Magnitude Response (dB) contains an axes object. The axes object with title Magnitude Response (dB) contains 3 objects of type line. These objects represent Equiripple design, Kaiser-window design.","Tex":"Magnitude Response (dB)","LaTex":[]}

Automatic Choice of Filter Design Technique

In addition to "Equiripple" and "Kaiser", the DesignMethod property of dsp.FIRHalfbandDecimator and dsp.FIRHalfbandInterpolator System objects can also be specified as "Auto". When DesignMethod is set to "Auto", the filter design method is chosen automatically by the object based on the filter design parameters.

Fs = 44.1e3;
TW = 1000; % Transition width
Ast = 60;  % 60 dB minimum attenuation in the stopband
autoHBFilter = dsp.FIRHalfbandDecimator(DesignMethod="Auto",...
    Specification="Transition width and stopband attenuation",...
    SampleRate=Fs,...
    TransitionWidth=TW,...
    StopbandAttenuation=Ast);
fvt = fvtool(autoHBFilter);
legend(fvt,"DesignMethod = Auto");

{"String":"Figure Figure 9: Magnitude Response (dB) contains an axes object. The axes object with title Magnitude Response (dB) contains 2 objects of type line. This object represents DesignMethod = Auto.","Tex":"Magnitude Response (dB)","LaTex":[]}

For the above filter specifications, you can observe from the magnitude response that the System object designs an equiripple filter. If the design constraints are very tight such as a very high stopband attenuation or a very narrow transition width, then the algorithm automatically chooses the Kaiser window method. The Kaiser window method is optimal to design filters with very tight specifications. However, if the design constraints are not tight, then the algorithm performs equiripple design.

The following illustrates a case where the filter specifications are too tight to perform equiripple design. The DesignMethod property of the object is set to "Equiripple". Hence the object attempts to design the filter using equiripple characteristics and the design fails to converge as you can observe from the frequency response of the filter.

Fs = 192e3;
TW = 100; % Transition width
Ast = 180;  % 180 dB minimum attenuation in the stopband
equirippleHBFilter = dsp.FIRHalfbandDecimator(DesignMethod="Equiripple",...
    TransitionWidth=TW,...
    StopbandAttenuation=Ast,...
    SampleRate=Fs);
fvt = fvtool(equirippleHBFilter);
Warning: Final filter order of 10448 is probably too high to optimally meet the constraints.
Warning: Design is not converging.  Number of iterations was 5
1) Check the resulting filter using freqz.
2) Check the specifications.
3) Filter order may be too large or too small.
4) For multiband filters, try making the transition regions more similar in width.
If err is very small, filter order may be too high
legend(fvt,"DesignMethod = Equiripple");

{"String":"Figure Figure 10: Magnitude Response (dB) contains an axes object. The axes object with title Magnitude Response (dB) contains 2 objects of type line. This object represents DesignMethod = Equiripple.","Tex":"Magnitude Response (dB)","LaTex":[]}

In this case, to design a filter that converges better, set the DesignMethod property to "Auto" or "Kaiser". The object designs the halfband filter using the Kaiser window method. Note that in this case, the designed filter does not accurately meet the tight design specifications. But the filter converges better as you can see by comparing the frequency response of the two filters.

Fs = 192e3;
TW = 100; % Transition width
Ast = 180;  % 180 dB minimum attenuation in the stopband
autoHBFilter = dsp.FIRHalfbandDecimator(DesignMethod="Auto",...
    TransitionWidth=TW,...
    StopbandAttenuation=Ast,...
    SampleRate=Fs);
fvt = fvtool(autoHBFilter);
Warning: The designed filter does not meet the filter specifications. Increase the Transition Width or reduce the Stopband Attenuation.
legend(fvt,"DesignMethod = Auto");

{"String":"Figure Figure 11: Magnitude Response (dB) contains an axes object. The axes object with title Magnitude Response (dB) contains 2 objects of type line. This object represents DesignMethod = Auto.","Tex":"Magnitude Response (dB)","LaTex":[]}

Equiripple Designs with Increasing Stopband Attenuation

Using the fdesign.interpolator and fdesign.decimator objects, you can also modify the shape of the stopband in equiripple design by specifying the optional "StopbandShape" argument of the design function.

Fs = 44.1e3;
TW = 1000/(Fs/2); % Transition width
Ast = 60;  % 60 dB minimum attenuation in the stopband
filtSpecs = fdesign.decimator(2,"halfband","TW,Ast",TW,Ast);
equirippleHBFilter1 = design(filtSpecs,"equiripple",...
    StopbandShape="1/f",StopbandDecay=4,SystemObject=true);
equirippleHBFilter2 = design(filtSpecs,"equiripple",...
    StopbandShape="linear",StopbandDecay=53.333,SystemObject=true);
fvt = fvtool(equirippleHBFilter1,equirippleHBFilter2,...
    Fs=Fs);
legend(fvt,"Stopband decaying as (1/f)^4","Stopband decaying linearly")

{"String":"Figure Figure 12: Magnitude Response (dB) contains an axes object. The axes object with title Magnitude Response (dB) contains 3 objects of type line. These objects represent Stopband decaying as (1/f)^4, Stopband decaying linearly.","Tex":"Magnitude Response (dB)","LaTex":[]}

Highpass Halfband Filters

A highpass halfband filter can be obtained from a lowpass halfband filter by changing the sign of every second coefficient. Alternatively, one can directly design a highpass halfband by setting the Type property of the fdesign.decimator object to "Highpass".

filtSpecs = fdesign.decimator(2,"halfband",...
    "TW,Ast",TW,Ast,Type="Highpass");
halfbandHPFilter = design(filtSpecs,"equiripple",...
    StopbandShape="linear",StopbandDecay=53.333,SystemObject=true);
fvt = fvtool(halfbandHPFilter,equirippleHBFilter2,Fs=Fs);
legend(fvt,"Highpass halfband filter","Lowpass halfband filter")

{"String":"Figure Figure 13: Magnitude Response (dB) contains an axes object. The axes object with title Magnitude Response (dB) contains 2 objects of type line. These objects represent Highpass halfband filter, Lowpass halfband filter.","Tex":"Magnitude Response (dB)","LaTex":[]}