How do filters work and how to simulate them (Signals and Systems Review Part4)

2025.10.03 Salzburg, Austria

Intro

In Understanding Signal Sampling and Sampling Theorem, we introduced (1). The difference between continuous and discrete signals (2). How are signals sampled (3). What is sampling theorem. We mentioned that to convert a discrete signal to a continuous signal, we can use a Low-Pass Filter (LPF) to block the high frequency component. Due to the wide application of filters in signal processing, it is something necessary to learn in Signals and Systems. This article will go through how filters work, and how they affect the signals.

Why do we need filters?

In addition to LPFs that filters out high frequency components, there are various types of filters. For example, the High-pass filter (HPF) that filters out low frequency components, the Band-pass filter (BPF) that allows certain frequency band to pass. The common characteristic is that these filters can let a certain frequency band pass and others blocked.

LPF、HPF、BPF

Fig. 1. How LPF works in time domain

The effect of LPF is shown in Fig. 1. The first subplot is a sin wave with noise that rapidly oscillates, but after filtering the noise is removed. Let’s convert it to frequency domain to see how it works.

Fig. 2. How LPF works in frequency domain

From the first subplot of Fig. 2 shows that this signal contains two frequency components, and LPF blocks the frequencies over 15Hz. This value is called cut-off frequency.

A HPF works similarly, as shown in Fig. 3. and Fig. 4.

Fig. 3. How HPF works in time domain
Fig. 4. How HPF works in frequency domain

We can also design a BPF which allows certain frequency band to band, while blocking other frequencies, as shown in Fig. 5. and Fig. 6.

Fig. 5. How BPF works in time domain
Fig. 6. How BPF works in frequency domain

The frequency band that is allowed to pass is called passband. The other that cannot pass is called stopband.

Impulse response and frequency response

So what exactly happens to a signal when it passes through a filter?
To answer this, we first need to understand the relationship among the input signal, the system, and the output signal.

Let’s take an intuitive example: when we cast white light towards a red filter, only red light passes through, while all other colors are blocked. In this analogy, the white light represents the input signal, the red filter represents the system, and the transmitted red light represents the output signal. Therefore, we can think of a filter as a “system”: the signal we want to process is the input, and the signal obtained after passing through the filter is the output.

When an impulse signal is applied to a system as input, the resulting output is called the impulse response. The impulse response is an important characteristic, because once we know how a system responds to an impulse, we can understand the system’s properties and analyze how it will affect any arbitrary signal.

As discussed in Understanding Convolution, feeding a signal into a system is equivalent to convolving the signal with the system’s impulse response in the time domain. The Fourier transform of the impulse response is called the frequency response.

In Understanding Signal Sampling and Sampling Theorem, we learned that convolution in the time domain corresponds to multiplication in the frequency domain. In other words, applying a signal to a system is equivalent to multiplying the input signal by the system’s frequency response in the frequency domain. This immediately tells us how the filter should be designed in the frequency domain, as illustrated below

Fig. 7. Frequency responses of three ideal filters

Fig.7. shows the frequency responses of three types of filters.
Take the first subplot as an example: when we multiply a frequency-domain signal by this frequency response, we find that the signal components below the cutoff frequency remain unchanged, while those above the cutoff frequency become zero.

Filters seem incredibly powerful, don’t they? In theory, we could take a time-domain signal, perform a Fourier transform to convert it to the frequency domain, write a program to simulate a filter that cleanly cuts off all unwanted frequency bands, and then apply an inverse Fourier transform to get a perfect signal back in the time domain.
So why are we still troubled by noise in practice?
The answer is that this is an idealized scenario — in real-world engineering, it’s not feasible.

Rectangular function and sinc function

The ideal approach above encounters two main obstacles when implemented in practice. (1) To compute a signal’s Fourier transform we need the complete signal (or a sufficiently long accumulated sequence) to have its frequency components. Therefore, in some real-time systems, such as a microphone used during a live speech, this method is not feasible.
(2) Because an ideal filter is impractical in the time domain, we should examine this point more closely.

If we look at the frequency responses of the three ideal filters in Fig. 7., we see they have the shape of rectangular functions. A rectangular function in the time domain corresponds to an infinitely long function called the sinc function, as shown in Fig. 8.

Fig. 8. A rectangular function in frequency domain correpsonds to a sinc function in time domain

sinc function is defined as

Equ. 1. Sinc function

The proof of Fig. 8. is given in Note 1 at the end of this article.

Also, a rectangular function in time domain is a sinc function in frequency domain, as shown in Fig. 9. below

Fig. 9. A rectangular function in time domain corresponds to a sinc function in frequency domain

The proof is provided in Note 2 at the end of this article.

So when we design a “perfect” filter in the frequency domain, we are effectively asking for an infinitely long signal in the time domain. Computers cannot store an infinite sequence, so at some point they must “stop” — in other words, if we try to implement a perfect frequency-domain filter, we will inevitably introduce distortion.

Therefore, real-world filters cannot be perfect. In practice, a filter affects an input signal’s (1) amplitude and (2) phase at each frequency. We’ll explain these in detail below.

First, after a time-domain signal is transformed into the frequency domain, it can be decomposed into amplitude and phase components. For an arbitrary signal x(t), the amplitude and phase of X(f) are given by

Equ. 2. Amplitude and phase of a signal

It’s more intuitive to understand that a filter changes the amplitude of different frequency components. For the frequency bands we want, we make sure their amplitudes don’t change. For others, we reduce the amplitude. In frequency domain, this corresponds to a multiplication

Equ. 3. Amplitude response

Additionally, a filter also changes the phase of a signal

Equ. 4. Phase repsonse

Something counter-intuitive is, for the amplitude the relation between the three is multiplication, while for phase it’s addition.

The concept of phase seems abstract, but it can simply be understood as the time delay of a signal. Let’s now delay a signal to see how it is changed in phase. Assume we have x(t) whose Fourier transform is X(f), and we delay x(t) by t_0, we get

Equ. 5. The phase changes correspond to signal delay

Therefore, delaying a signal of t_0, is equivalent to changing the phase of -2πft_0. f is a variant, it contains different phase changes in different frequency components. The phase response of a filter is shown below

Fig. 10. The phase response that makes signal doesn’t distort

In the earlier part of this article, we discussed filters under the assumption that there was no delay — that is, the signal passes from input to output instantaneously.
In reality, however, due to the hardware and software design of filters, some delay is inevitable. To maintain an undistorted signal, the ideal condition is that all frequency components experience the same relative delay.

How Filters Are Implemented: Analog vs. Digital

We already know that ideal filters are not achievable in practical engineering. So, how do we build those imperfect but useful filters in the real world?
There are two tpyes: Analog Filters and Digital Filters.

  • Analog filters are physical circuits made of components such as resistors, capacitors, inductors, and operational amplifiers. They process continuous analog voltage or current signals.
  • Digital filters, on the other hand, are mathematical algorithms running on processors (such as CPUs, DSPs, or microcontrollers) that process discrete digital signals obtained after sampling.

The following figure lists three common types of filters. Each of these filters can be realized either using electronic components or simulated via software. Their frequency responses consist of two parts: the amplitude response and the phase response, which describe how different frequency components of the signal are affected in amplitude and phase as they pass through the system. Due to space limitations, we won’t discuss each filter’s application scenarios in detail, but we’ll briefly summarize their key characteristics.

Fig. 11. Common filters

It’s worth noting that the amplitude responses of these filters all form smooth curves between the passband and the stopband — confirming what we mentioned earlier: a perfectly “sharp” cutoff filter is impractical in reality.

Butterworth Filter

Characteristics: The most balanced and smooth in performance. Its response within the passband is maximally flat, meaning it introduces no additional ripples or oscillations to the signal. The transition band is relatively gentle, making it a mild but very reliable general-purpose choice.

Advantages: Flat passband with no ripple.
Disadvantages: The roll-off (attenuation slope) is not very steep.

Chebyshev Type I Filter

Characteristics: Designed for the steepest possible roll-off. It can switch very sharply from “pass” to “stop,” producing a clean and decisive filtering effect — ideal for separating closely spaced frequencies.

Advantages: For the same filter order, it achieves a much steeper roll-off than the Butterworth filter.
Disadvantages: The trade-off is the presence of small amplitude fluctuations in the passband, known as ripples.

Bessel Filter

Characteristics: Prioritizes preserving the original waveform of the signal. It has the most linear phase response among all filter types, meaning the delay across frequencies is consistent. As a result, it minimizes ringing effects and other time-domain distortions.

Advantages: Best phase response and minimal waveform distortion.
Disadvantages: The roll-off slope is the gentlest among all types.

Conclusion

As the characteristics above show, many problems in signal processing involve trade-offs. Enhancing one aspect of performance often comes at the expense of another. You’ll see this principle of compromise appear repeatedly throughout the upcoming sections of our Signal and System Review series.

Note 1: A rectangular function in frequency domain converts to time domain

Equ. 6. A rectangular function in frequency domain converts to time domain

Note 2: A rectangular function in time domain converts to frequency domain

Python simulation

# Fig. 1. and  2.
import numpy as np
from scipy import signal
import matplotlib.pyplot as plt
# --- 1. Create the Signal ---
# Set signal parameters
sampling_rate = 1000 # Sampling frequency (Hz)
T = 1 # Signal duration (seconds)
t = np.linspace(0, T, int(T * sampling_rate), endpoint=False) # Time axis
# Create a composite signal: a low-frequency signal mixed with high-frequency noise
# a. The useful low-frequency signal (e.g., 5 Hz)
f_low = 5
signal_low = np.sin(2 * np.pi * f_low * t)
# b. The unwanted high-frequency noise (e.g., 50 Hz)
f_high = 50
noise = 0.5 * np.sin(2 * np.pi * f_high * t)
# c. The original signal (low-frequency signal + noise)
original_signal = signal_low + noise
# --- 2. Design and Apply the Low-Pass Filter ---
# Set the cutoff frequency for the low-pass filter
# We want to keep the 5 Hz signal and remove the 50 Hz noise,
# so we set the cutoff frequency somewhere in between, like 15 Hz.
cutoff_freq = 15 # Hz
# Design a Butterworth low-pass filter
# Wn: Normalized cutoff frequency (cutoff_freq / (0.5 * sampling_rate))
nyquist_freq = 0.5 * sampling_rate
normal_cutoff = cutoff_freq / nyquist_freq
# b, a are the filter coefficients
b, a = signal.butter(4, normal_cutoff, btype='low', analog=False)
# Apply the filter to the original signal
filtered_signal = signal.lfilter(b, a, original_signal)
# --- 3. Visualize the Results ---
# Figure 1: Time Domain Comparison
plt.figure(figsize=(12, 6))
plt.subplot(2, 1, 1)
plt.plot(t, original_signal, 'b-', alpha=0.6, label='Original Signal (with High-Freq Noise)')
plt.title('Time Domain Analysis')
plt.xlabel('Time (s)')
plt.ylabel('Amplitude')
plt.legend()
plt.grid(True)
plt.subplot(2, 1, 2)
plt.plot(t, filtered_signal, 'r-', linewidth=2, label='Filtered Signal (Low Frequencies Kept)')
plt.xlabel('Time (s)')
plt.ylabel('Amplitude')
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()
# Figure 2: Frequency Domain Comparison
# Calculate the Fast Fourier Transform (FFT)
N = len(original_signal)
yf_original = np.fft.fft(original_signal)
yf_filtered = np.fft.fft(filtered_signal)
xf = np.fft.fftfreq(N, 1 / sampling_rate)
plt.figure(figsize=(12, 6))
plt.subplot(2, 1, 1)
# We only need to plot the positive frequencies
plt.plot(xf[:N//2], 2.0/N * np.abs(yf_original[:N//2]), 'b-', alpha=0.6, label='Original Signal Spectrum')
plt.axvline(cutoff_freq, color='k', linestyle='--', label=f'Cutoff Frequency ({cutoff_freq} Hz)')
plt.title('Frequency Domain Analysis')
plt.xlabel('Frequency (Hz)')
plt.ylabel('Amplitude')
plt.legend()
plt.grid(True)
plt.subplot(2, 1, 2)
plt.plot(xf[:N//2], 2.0/N * np.abs(yf_filtered[:N//2]), 'r-', linewidth=2, label='Filtered Signal Spectrum')
plt.axvline(cutoff_freq, color='k', linestyle='--', label=f'Cutoff Frequency ({cutoff_freq} Hz)')
plt.xlabel('Frequency (Hz)')
plt.ylabel('Amplitude')
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()
# Fig. 3. and 4.
import numpy as np
from scipy import signal
import matplotlib.pyplot as plt
# --- 1. Create the Signal ---
# Set signal parameters
sampling_rate = 1000 # Sampling frequency (Hz)
T = 1 # Signal duration (seconds)
t = np.linspace(0, T, int(T * sampling_rate), endpoint=False) # Time axis
# Create a composite signal: a high-frequency signal mixed with low-frequency noise
# a. The useful high-frequency signal (e.g., 50 Hz)
f_high = 50
signal_high = np.sin(2 * np.pi * f_high * t)
# b. The unwanted low-frequency noise or "rumble" (e.g., 2 Hz)
f_low = 2
noise_rumble = 0.7 * np.sin(2 * np.pi * f_low * t)
# c. The original signal (high-frequency signal + low-frequency noise)
original_signal = signal_high + noise_rumble
# --- 2. Design and Apply the High-Pass Filter ---
# Set the cutoff frequency for the high-pass filter
# We want to keep the 50 Hz signal and remove the 2 Hz rumble,
# so we set the cutoff frequency somewhere in between, like 20 Hz.
cutoff_freq = 20 # Hz
# Design a Butterworth high-pass filter
# Wn: Normalized cutoff frequency (cutoff_freq / (0.5 * sampling_rate))
nyquist_freq = 0.5 * sampling_rate
normal_cutoff = cutoff_freq / nyquist_freq
# b, a are the filter coefficients
# The only change from the LPF is setting btype='high'
b, a = signal.butter(4, normal_cutoff, btype='high', analog=False)
# Apply the filter to the original signal
filtered_signal = signal.lfilter(b, a, original_signal)
# --- 3. Visualize the Results ---
# Figure 1: Time Domain Comparison
plt.figure(figsize=(12, 6))
plt.subplot(2, 1, 1)
plt.plot(t, original_signal, 'b-', alpha=0.6, label='Original Signal (with Low-Freq Rumble)')
plt.title('Time Domain Analysis')
plt.xlabel('Time (s)')
plt.ylabel('Amplitude')
plt.legend()
plt.grid(True)
plt.subplot(2, 1, 2)
plt.plot(t, filtered_signal, 'r-', linewidth=2, label='Filtered Signal (High Frequencies Kept)')
plt.xlabel('Time (s)')
plt.ylabel('Amplitude')
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()
# Figure 2: Frequency Domain Comparison
# Calculate the Fast Fourier Transform (FFT)
N = len(original_signal)
yf_original = np.fft.fft(original_signal)
yf_filtered = np.fft.fft(filtered_signal)
xf = np.fft.fftfreq(N, 1 / sampling_rate)
plt.figure(figsize=(12, 6))
plt.subplot(2, 1, 1)
# We only need to plot the positive frequencies
plt.plot(xf[:N//2], 2.0/N * np.abs(yf_original[:N//2]), 'b-', alpha=0.6, label='Original Signal Spectrum')
plt.axvline(cutoff_freq, color='k', linestyle='--', label=f'Cutoff Frequency ({cutoff_freq} Hz)')
plt.title('Frequency Domain Analysis')
plt.xlabel('Frequency (Hz)')
plt.ylabel('Amplitude')
plt.legend()
plt.grid(True)
plt.subplot(2, 1, 2)
plt.plot(xf[:N//2], 2.0/N * np.abs(yf_filtered[:N//2]), 'r-', linewidth=2, label='Filtered Signal Spectrum')
plt.axvline(cutoff_freq, color='k', linestyle='--', label=f'Cutoff Frequency ({cutoff_freq} Hz)')
plt.xlabel('Frequency (Hz)')
plt.ylabel('Amplitude')
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()
# Fig. 5. and 6.
import numpy as np
from scipy import signal
import matplotlib.pyplot as plt
# --- 1. Create the Signal ---
# Set signal parameters
sampling_rate = 1000 # Sampling frequency (Hz)
T = 1 # Signal duration (seconds)
t = np.linspace(0, T, int(T * sampling_rate), endpoint=False) # Time axis
# Create a composite signal: a mid-frequency signal of interest corrupted by both low and high-frequency noise
# a. The useful mid-frequency signal (e.g., 50 Hz)
f_mid = 50
signal_mid = np.sin(2 * np.pi * f_mid * t)
# b. Unwanted low-frequency noise or "rumble" (e.g., 5 Hz)
f_low = 5
noise_low = 0.7 * np.sin(2 * np.pi * f_low * t)
# c. Unwanted high-frequency noise or "hiss" (e.g., 150 Hz)
f_high = 150
noise_high = 0.5 * np.sin(2 * np.pi * f_high * t)
# d. The original signal (mid-frequency signal + low-freq noise + high-freq noise)
original_signal = signal_mid + noise_low + noise_high
# --- 2. Design and Apply the Band-Pass Filter ---
# Set the cutoff frequencies for the band-pass filter
# We want to isolate the 50 Hz signal.
# So, we define a "pass-band" around it, for example, from 40 Hz to 60 Hz.
lowcut_freq = 40.0 # Lower cutoff frequency (Hz)
highcut_freq = 60.0 # Upper cutoff frequency (Hz)
# Design a Butterworth band-pass filter
# Wn: A list of the two normalized cutoff frequencies
nyquist_freq = 0.5 * sampling_rate
low = lowcut_freq / nyquist_freq
high = highcut_freq / nyquist_freq
# The only changes from previous filters are btype='band' and providing [low, high]
b, a = signal.butter(4, [low, high], btype='band', analog=False)
# Apply the filter to the original signal
filtered_signal = signal.lfilter(b, a, original_signal)
# --- 3. Visualize the Results ---
# Figure 1: Time Domain Comparison
plt.figure(figsize=(12, 6))
plt.subplot(2, 1, 1)
plt.plot(t, original_signal, 'b-', alpha=0.6, label='Original Signal (with Mixed Noise)')
plt.title('Time Domain Analysis')
plt.xlabel('Time (s)')
plt.ylabel('Amplitude')
plt.legend()
plt.grid(True)
plt.subplot(2, 1, 2)
plt.plot(t, filtered_signal, 'r-', linewidth=2, label='Filtered Signal (Mid Frequencies Kept)')
plt.xlabel('Time (s)')
plt.ylabel('Amplitude')
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()
# Figure 2: Frequency Domain Comparison
# Calculate the Fast Fourier Transform (FFT)
N = len(original_signal)
yf_original = np.fft.fft(original_signal)
yf_filtered = np.fft.fft(filtered_signal)
xf = np.fft.fftfreq(N, 1 / sampling_rate)
plt.figure(figsize=(12, 6))
plt.subplot(2, 1, 1)
# We only need to plot the positive frequencies
plt.plot(xf[:N//2], 2.0/N * np.abs(yf_original[:N//2]), 'b-', alpha=0.6, label='Original Signal Spectrum')
plt.axvline(lowcut_freq, color='k', linestyle='--', label=f'Low Cutoff ({lowcut_freq} Hz)')
plt.axvline(highcut_freq, color='k', linestyle='--', label=f'High Cutoff ({highcut_freq} Hz)')
plt.title('Frequency Domain Analysis')
plt.xlabel('Frequency (Hz)')
plt.ylabel('Amplitude')
plt.legend()
plt.grid(True)
plt.subplot(2, 1, 2)
plt.plot(xf[:N//2], 2.0/N * np.abs(yf_filtered[:N//2]), 'r-', linewidth=2, label='Filtered Signal Spectrum')
plt.axvline(lowcut_freq, color='k', linestyle='--')
plt.axvline(highcut_freq, color='k', linestyle='--')
plt.xlabel('Frequency (Hz)')
plt.ylabel('Amplitude')
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()
# Fig. 7.
import numpy as np
import matplotlib.pyplot as plt
# --- Create a frequency axis ---
f = np.linspace(0, 150, 1500)
# --- Define filter parameters ---
low_cutoff = 40 # Cut-off frequency for the low-pass filter
high_cutoff = 80 # Cut-off frequency for the high-pass filter
band_low = 50 # Lower cut-off for the band-pass filter
band_high = 90 # Upper cut-off for the band-pass filter
# --- Generate the ideal frequency responses ---
# 1. Low-Pass Filter (LPF)
lpf_response = np.zeros_like(f)
lpf_response[f <= low_cutoff] = 1.0
# 2. High-Pass Filter (HPF)
hpf_response = np.zeros_like(f)
hpf_response[f >= high_cutoff] = 1.0
# 3. Band-Pass Filter (BPF)
bpf_response = np.zeros_like(f)
bpf_response[(f >= band_low) & (f <= band_high)] = 1.0
# --- Plotting ---
fig, (ax1, ax2, ax3) = plt.subplots(3, 1, figsize=(10, 9), sharex=True)
fig.suptitle('Ideal Frequency Responses of Basic Filters', fontsize=16)
# Plot 1: Low-Pass Filter
ax1.plot(f, lpf_response, 'b', linewidth=2)
ax1.set_title('Low-Pass Filter (LPF)')
ax1.fill_between(f, 0, lpf_response, color='blue', alpha=0.2, label='Pass-band')
ax1.text(low_cutoff, -0.1, r'$F_c$', ha='center', fontsize=14) # Fc for cut-off
ax1.set_ylabel('Gain (Amplitude)')
ax1.grid(True)
ax1.legend()
# Plot 2: High-Pass Filter
ax2.plot(f, hpf_response, 'g', linewidth=2)
ax2.set_title('High-Pass Filter (HPF)')
ax2.fill_between(f, 0, hpf_response, color='green', alpha=0.2, label='Pass-band')
ax2.text(high_cutoff, -0.1, r'$F_c$', ha='center', fontsize=14)
ax2.set_ylabel('Gain (Amplitude)')
ax2.grid(True)
ax2.legend()
# Plot 3: Band-Pass Filter
ax3.plot(f, bpf_response, 'r', linewidth=2)
ax3.set_title('Band-Pass Filter (BPF)')
ax3.fill_between(f, 0, bpf_response, color='red', alpha=0.2, label='Pass-band')
ax3.text(band_low, -0.1, r'$F_{c1}$', ha='center', fontsize=14) # Fc1 for lower cut-off
ax3.text(band_high, -0.1, r'$F_{c2}$', ha='center', fontsize=14) # Fc2 for upper cut-off
ax3.set_xlabel('Frequency (Hz)')
ax3.set_ylabel('Gain (Amplitude)')
ax3.grid(True)
ax3.legend()
plt.ylim(-0.2, 1.2)
plt.tight_layout(rect=[0, 0.03, 1, 0.95])
plt.show()
# Fig. 8.
import numpy as np
import matplotlib.pyplot as plt
# --- Parameters (for plotting purposes only) ---
A = 1.0 # Symbolic height
W = 4.0 # Symbolic width
# --- Time Domain: Sinc Function ---
t = np.linspace(-4/W, 4/W, 2000)
sinc_func = A * W * np.sinc(t * W)
# --- Frequency Domain: Rectangular Pulse ---
f = np.linspace(-W, W, 1000)
rect_pulse = np.zeros_like(f)
rect_pulse[np.abs(f) <= W/2] = A
# --- Plotting ---
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 8))
fig.suptitle('Inverse Fourier Transform of a Rectangular Pulse (Schematic)', fontsize=16)
# --- Subplot 1: Sinc Function in Time Domain ---
ax1.plot(t, sinc_func, 'r', linewidth=2)
ax1.set_title('Time Domain (Impulse Response)')
# Hide axis numerical labels
ax1.set_yticklabels([])
ax1.set_xticklabels([])
ax1.set_xticks([])
ax1.set_yticks([])
# Draw axes lines
ax1.axhline(0, color='black', linewidth=0.5)
ax1.axvline(0, color='black', linewidth=0.5)
# Add symbolic labels
ax1.text(0.1, A * W, 'AW', fontsize=14, ha='left', va='center')
ax1.text(1/W, -0.2, '1/W', ha='center', va='top', fontsize=12)
ax1.text(-1/W, -0.2, '-1/W', ha='center', va='top', fontsize=12)
ax1.set_xlabel('Time (t)')
ax1.set_ylabel('Amplitude')
# --- Subplot 2: Rectangular Pulse in Frequency Domain ---
ax2.plot(f, rect_pulse, 'b', linewidth=2)
ax2.set_title('Frequency Domain (Frequency Response)')
# Hide axis numerical labels
ax2.set_yticklabels([])
ax2.set_xticklabels([])
ax2.set_xticks([])
ax2.set_yticks([])
# Draw axes lines
ax2.axhline(0, color='black', linewidth=0.5)
ax2.axvline(0, color='black', linewidth=0.5)
# Add symbolic labels
ax2.text(0, A + 0.05, 'A', fontsize=14, ha='center', va='bottom')
ax2.text(W/2, -0.05, 'W/2', ha='center', va='top', fontsize=12)
ax2.text(-W/2, -0.05, '-W/2', ha='center', va='top', fontsize=12)
ax2.set_ylim(-0.2, A * 1.2)
ax2.set_xlabel('Frequency (f)')
ax2.set_ylabel('Amplitude')
plt.tight_layout(rect=[0, 0, 1, 0.95])
plt.show()
# Fig. 9.
import numpy as np
import matplotlib.pyplot as plt
# --- Parameters (for plotting purposes only) ---
A = 1.0 # Symbolic height
T = 2.0 # Symbolic width
# --- Time Domain: Rectangular Pulse ---
t = np.linspace(-T, T, 1000)
rect_pulse = np.zeros_like(t)
rect_pulse[np.abs(t) <= T/2] = A
# --- Frequency Domain: Sinc Function ---
f = np.linspace(-4/T, 4/T, 2000)
sinc_func = A * T * np.sinc(f * T)
# --- Plotting ---
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 8))
fig.suptitle('Fourier Transform of a Rectangular Pulse (Schematic)', fontsize=16)
# --- Subplot 1: Rectangular Pulse in Time Domain ---
ax1.plot(t, rect_pulse, 'b', linewidth=2)
ax1.set_title('Time Domain')
# Hide axis numerical labels
ax1.set_yticklabels([])
ax1.set_xticklabels([])
ax1.set_xticks([])
ax1.set_yticks([])
# Draw axes lines
ax1.axhline(0, color='black', linewidth=0.5)
ax1.axvline(0, color='black', linewidth=0.5)
# Add symbolic labels
ax1.text(0, A + 0.05, 'A', fontsize=14, ha='center', va='bottom')
ax1.text(T/2, -0.05, 'T/2', ha='center', va='top', fontsize=12)
ax1.text(-T/2, -0.05, '-T/2', ha='center', va='top', fontsize=12)
ax1.set_ylim(-0.2, A * 1.2)
ax1.set_xlabel('Time (t)')
ax1.set_ylabel('Amplitude')
# --- Subplot 2: Sinc Function in Frequency Domain ---
ax2.plot(f, sinc_func, 'r', linewidth=2)
ax2.set_title('Frequency Domain')
# Hide axis numerical labels
ax2.set_yticklabels([])
ax2.set_xticklabels([])
ax2.set_xticks([])
ax2.set_yticks([])
# Draw axes lines
ax2.axhline(0, color='black', linewidth=0.5)
ax2.axvline(0, color='black', linewidth=0.5)
# Add symbolic labels
ax2.text(0.1, A * T, 'AT', fontsize=14, ha='left', va='center')
ax2.text(1/T, -0.1, '1/T', ha='center', va='top', fontsize=12)
ax2.text(-1/T, -0.1, '-1/T', ha='center', va='top', fontsize=12)
ax2.set_xlabel('Frequency (f)')
ax2.set_ylabel('Amplitude')
plt.tight_layout(rect=[0, 0, 1, 0.95])
plt.show()
# Fig. 10.
import numpy as np
import matplotlib.pyplot as plt
# --- Parameters ---
# Set a constant time delay t0 (e.g., 5 milliseconds)
t0 = 0.005 # seconds
# Create a frequency axis (0 to 100 Hz)
f = np.linspace(0, 100, 500)
# --- Calculate the Linear Phase Response ---
# Based on the formula we proved: phi = -2 * pi * f * t0
phase = -2 * np.pi * f * t0
# --- Plotting ---
# The following lines for setting a Chinese font are no longer necessary,
# but are kept here for reference.
# plt.rcParams['font.sans-serif'] = ['Microsoft JhengHei', 'Heiti TC', 'sans-serif']
# plt.rcParams['axes.unicode_minus'] = False
fig, ax = plt.subplots(figsize=(10, 6))
# Plot the line representing the linear phase
ax.plot(f, phase, 'r-', linewidth=2.5, label='Linear Phase Response')
# Add title and axis labels
ax.set_title('Phase Response of a Filter with Constant Time Delay (Linear Phase)', fontsize=16)
ax.set_xlabel('Frequency (Hz)', fontsize=12)
ax.set_ylabel('Phase (radians)', fontsize=12)
# Add a grid
ax.grid(True)
# Add annotations to make the plot easier to understand
# Display the formula and the value of t0
formula_text = fr'$\phi(f) = -2\pi f t_0$'
t0_text = f'Here $t_0 = {t0 * 1000:.0f}$ ms (Constant Delay)'
ax.text(60, -1, formula_text, fontsize=14, bbox=dict(facecolor='white', alpha=0.5))
ax.text(60, -1.5, t0_text, fontsize=12, bbox=dict(facecolor='white', alpha=0.5))
# Ensure the origin (0,0) is clearly visible
ax.axhline(0, color='black', linewidth=0.5)
ax.axvline(0, color='black', linewidth=0.5)
ax.legend()
plt.tight_layout()
plt.show()
# Fig. 11.
import numpy as np
from scipy import signal
import matplotlib.pyplot as plt
# --- Common Filter Parameters ---
sampling_rate = 1000.0
cutoff_freq = 100.0 # Cutoff frequency for all filters
filter_order = 4 # Order of the filters
# Normalize the cutoff frequency
nyquist_freq = 0.5 * sampling_rate
normal_cutoff = cutoff_freq / nyquist_freq
# --- 1. Design Different Filter Types ---
# a. Butterworth Filter
b_butter, a_butter = signal.butter(filter_order, normal_cutoff, btype='low', analog=False)
# b. Chebyshev Type I Filter
# rp: maximum ripple in the passband (dB)
rp = 1
b_cheby, a_cheby = signal.cheby1(filter_order, rp, normal_cutoff, btype='low', analog=False)
# c. Bessel Filter
b_bessel, a_bessel = signal.bessel(filter_order, normal_cutoff, btype='low', analog=False)
# --- 2. Calculate Frequency Responses ---
w_butter, h_butter = signal.freqz(b_butter, a_butter, worN=8000)
w_cheby, h_cheby = signal.freqz(b_cheby, a_cheby, worN=8000)
w_bessel, h_bessel = signal.freqz(b_bessel, a_bessel, worN=8000)
# Convert frequency axis to Hz
freq_axis = (w_butter / np.pi) * nyquist_freq
# --- 3. Plotting ---
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 10))
fig.suptitle(f'Comparison of Common Filter Types (Order={filter_order}, Cutoff={cutoff_freq} Hz)', fontsize=16)
# --- Subplot 1: Magnitude Response ---
ax1.plot(freq_axis, 20 * np.log10(abs(h_butter)), 'b', label='Butterworth')
ax1.plot(freq_axis, 20 * np.log10(abs(h_cheby)), 'g', label='Chebyshev I')
ax1.plot(freq_axis, 20 * np.log10(abs(h_bessel)), 'r', label='Bessel')
ax1.set_title('Magnitude Response')
ax1.set_xlabel('Frequency (Hz)')
ax1.set_ylabel('Gain (dB)')
ax1.set_ylim(-60, 5)
ax1.axvline(cutoff_freq, color='k', linestyle='--', label=f'Cutoff Frequency ({cutoff_freq} Hz)')
ax1.axhline(-3, color='grey', linestyle=':', label='-3 dB Point')
ax1.grid(True, which='both')
ax1.legend()
# --- Subplot 2: Phase Response ---
ax2.plot(freq_axis, np.unwrap(np.angle(h_butter)), 'b', label='Butterworth')
ax2.plot(freq_axis, np.unwrap(np.angle(h_cheby)), 'g', label='Chebyshev I')
ax2.plot(freq_axis, np.unwrap(np.angle(h_bessel)), 'r', label='Bessel')
ax2.set_title('Phase Response')
ax2.set_xlabel('Frequency (Hz)')
ax2.set_ylabel('Phase (radians)')
ax2.grid(True)
ax2.legend()
ax2.set_xlim(0, cutoff_freq * 2) # Focus on the passband and transition band
plt.tight_layout(rect=[0, 0, 1, 0.96])
plt.show()

Learn more about How do filters work and how to simulate them (Signals and Systems Review Part4)

Leave a Reply