Mở đầu: filter đầu tiên ai cũng viết, và vì sao nó "tồi"

Bài 4 định nghĩa: một hệ LTI được đặc trưng hoá TRỌN VẸN bởi đáp ứng xung $h[n]$, và đầu ra là phép tích chập $y[n] = x[n] * h[n]$. Đó chính xác là những gì một filter FIR (Finite Impulse Response) làm — bài này trả đúng cổ tức đã hứa từ Bài 4.

Filter đầu tiên hầu như ai học DSP cũng tự viết: trung bình trượt (moving average) — cộng vài mẫu gần nhau rồi chia trung bình. Nó mượt tín hiệu thật, nhưng đáp ứng tần số của nó lại có hình dạng sinc lởm chởm, chặn dải kém — chính là bộ lọc âm thầm chạy phía sau Series 13 Bài 10 (ADC) mà không ai giải thích vì sao nó lại có hình dạng phổ kỳ lạ đó. Bài này giải thích, rồi xây dựng một filter thiết kế CÓ CHỦ ĐÍCH, đúng đắn hơn hẳn.


📚 Điều kiện tiên quyết
Bắt buộc: Bài 4 (tích chập, đáp ứng xung), Bài 5 (đáp ứng tần số) và Bài 7 (hàm cửa sổ — tái sử dụng trực tiếp trong thiết kế windowed-sinc).

1. Filter = hệ LTI có chủ đích

FIR nghĩa đen: đáp ứng xung $h[n]$ có ĐỘ DÀI HỮU HẠN (finite). Trung bình trượt $N$ điểm chính là 1 FIR đặc biệt: $h[n] = 1/N$ cho $N$ mẫu đầu, còn lại 0. Verify bằng self-test: đưa nhiễu trắng qua bộ lọc này (convolve với $h$), phương sai giảm rõ rệt so với tín hiệu gốc — nó THỰC SỰ làm mượt. Nhưng đáp ứng tần số của trung bình trượt có hình sinc với các thuỳ phụ lởm chởm (giống Bài 7!) — chặn dải kém, không kiểm soát được chính xác tần số cắt. Mục 2 thiết kế filter ĐÚNG CHỦ ĐÍCH hơn.

2. Thiết kế từ "giấc mơ": low-pass lý tưởng → sinc vô hạn

Low-pass "trong mơ" hoàn hảo: giữ NGUYÊN VẸN mọi tần số dưới cutoff, TRIỆT TIÊU HOÀN TOÀN mọi tần số trên cutoff — về mặt hình học, đáp ứng tần số là một GẠCH CHỮ NHẬT hoàn hảo. Biến đổi Fourier ngược của một gạch chữ nhật (Bài 6 quen thuộc) chính là hàm sinc — nhưng trải dài VÔ HẠN về cả 2 hướng thời gian, nghĩa là filter này cần biết trước cả tương lai lẫn quá khứ vô hạn — không thể cài đặt được (không nhân quả).

Đường ra thực dụng — windowed-sinc — gồm đúng 3 bước:

  1. Cắt sinc vô hạn về một số tap hữu hạn ($N$ hệ số).
  2. Dịch về bên phải $((N-1)/2$ mẫu) để phần "quan trọng nhất" (đỉnh sinc) rơi vào vùng có thể tính được — biến filter thành NHÂN QUẢ.
  3. Nhân với cửa sổ (Bài 7 tái xuất đúng vai) — cắt "cứng" (như cửa sổ chữ nhật) tạo gợn sóng dải thông xấu (hiện tượng Gibbs), cửa sổ mượt (Hann/Hamming/Blackman) giảm gợn đáng kể.
fir_windowed_sinc.js (trích engine dsp-core.js)
function firLowpassDesign(numTaps, cutoffNorm, windowFn) {
  const M = (numTaps - 1) / 2;      // dich de nhan qua
  const win = windowFn(numTaps);     // cua so Bai 7 - KHONG viet lai
  const h = new Array(numTaps);
  for (let n = 0; n < numTaps; n++) {
    const k = n - M;
    const ideal = 2 * cutoffNorm * sinc(2 * cutoffNorm * k); // sinc ly tuong, DA CAT + DICH
    h[n] = ideal * win[n];           // NHAN voi cua so
  }
  return h;
}

Số tap càng NHIỀU, sinc bị cắt càng gần với bản vô hạn lý tưởng → dải chuyển tiếp (transition band) càng DỐC (gần lý tưởng hơn) — nhưng filter càng dài (Mục 4 pitfall).

3. Linear phase — món quà của đối xứng

Vì đáp ứng xung windowed-sinc luôn ĐỐI XỨNG quanh tâm ($h[n] = h[N-1-n]$ — verify bằng self-test: sai số dưới $10^{-9}$, thực chất là 0 tuyệt đối, chỉ giới hạn bởi làm tròn dấu phẩy động), FIR loại này có một tính chất TOÁN HỌC quý giá: linear phase — MỌI tần số đi qua filter đều bị trễ ĐÚNG BẰNG NHAU một lượng cố định, gọi là group delay, luôn bằng $(N{-}1)/2$ mẫu (verify: $N=51$ tap cho group delay đúng 25 mẫu).

Vì sao quý giá: nếu các tần số bị trễ KHÔNG ĐỀU nhau (như hầu hết filter IIR ở Bài 11), dạng sóng gốc bị MÉO — các thành phần tần số "đến nơi" ở thời điểm khác nhau, làm hỏng hình dạng ban đầu. Với linear phase, dạng sóng CHỈ bị trễ, không hề méo — đây là lý do FIR (không phải IIR) được chọn cho mastering âm thanh chuyên nghiệp, thiết bị đo lường chính xác, và filter bank — bất kỳ nơi nào pha "là vàng".

🔬 Đào sâu: đối xứng đảm bảo linear phase bằng cách nào?
Về mặt toán học, đáp ứng tần số của 1 FIR đối xứng luôn tách được thành $H(f) = A(f) \cdot e^{-j2\pi f (N-1)/2}$, trong đó $A(f)$ là một hàm THỰC (không phải phức) mô tả biên độ theo tần số. Phần pha $-2\pi f (N{-}1)/2$ là một đường THẲNG qua gốc toạ độ theo biến $f$ — đúng nghĩa đen của "linear phase". Vì độ dốc của đường thẳng đó không đổi với MỌI $f$, đạo hàm của nó (chính là group delay) là một HẰNG SỐ $(N{-}1)/2$, không phụ thuộc tần số — đây không phải một tính chất "thường thấy" mà là hệ quả TẤT YẾU của riêng cấu trúc đối xứng, không FIR bất đối xứng hay IIR nào có được miễn phí như vậy.

4. Biến hình từ low-pass: high-pass & band-pass

Không cần thiết kế lại từ đầu — 2 phép biến hình đơn giản tái sử dụng NGUYÊN low-pass vừa xây:

Loại filter Cách biến hình Verified
High-pass Spectral inversion: xung đơn vị (tại tâm) TRỪ ĐI low-pass cùng cutoff DC ≈ 0 (chặn), gần Nyquist ≈ 1 (thông) — vẫn đối xứng hoàn hảo
Band-pass Dịch tần: low-pass (băng thông mong muốn) NHÂN với cosin tại tần số trung tâm DC ≈ 0, tại tần số trung tâm ≈ 1, gần Nyquist ≈ 0 — cả 2 đầu đều bị chặn
⚠️ Cạm bẫy: filter càng "gắt" càng dài → trễ càng lớn
Muốn dải chuyển tiếp dốc hơn (chặn dải kém → chặn dải cực tốt gần như tức thì sau cutoff) BẮT BUỘC phải tăng số tap $N$ — nhưng group delay $(N{-}1)/2$ tăng THEO ĐÚNG TỶ LỆ. Một filter FIR "gắt" đủ để chặn dải cực tốt có thể cần hàng nghìn tap, tương đương độ trễ hàng chục mili-giây — chấp nhận được khi xử lý file ngoại tuyến, nhưng có thể KHÔNG chấp nhận được cho xử lý thời gian thực (điều khiển động cơ, gọi thoại) cần phản hồi tức thì. Đây chính là động lực gieo mầm cho filter IIR ở Bài 11 sắp tới — đệ quy, ít tap hơn nhiều cho cùng độ dốc, đổi lại mất linear phase.

5. Số đo thật từ self-test

Verify bằng node dsp-core.js, với low-pass $N=51$ tap, cửa sổ Hann, cutoff chuẩn hoá 0,1:

Đại lượng Giá trị verified
Đối xứng $h[i] = h[N-1-i]$ Sai số < $10^{-9}$ (thực chất là 0 tuyệt đối)
Group delay Đúng 25 mẫu ($(51{-}1)/2$)
Đáp ứng tại DC (f=0) ≈ 1,00 (dải thông không suy hao)
Đáp ứng tại tần số cắt (f=0,1) ≈ 0,50 (điểm -6dB kinh điển của windowed-sinc)
Đáp ứng dải chặn (f=0,2) < 0,001 (chặn tốt)
Đáp ứng gần Nyquist (f=0,4) < 0,0001 (gần như triệt tiêu hoàn toàn)

6. Thực hành: FIR Designer

Kéo tần số cắt và số tap để xem $h[n]$ (đáp ứng xung) và $|H(f)|$ (đáp ứng tần số, thang dB) thay đổi trực tiếp — cả hai đều tính bằng đúng firLowpassDesign() vừa verify ở trên:

🎚️ FIR Designer — DSPJS thật

Đáp ứng xung h[n]:

Đáp ứng tần số |H(f)| (dB):

Đang khởi tạo…
fir_designer_demo.js (đúng logic đang chạy ở tab Xem trước)
import { firLowpassDesign, hannWindow, firFrequencyResponse, dftMagnitude, magnitudeToDb, firGroupDelay } from './dsp-core.js';

function updateFilter(cutoffNorm, numTaps) {
  const h = firLowpassDesign(numTaps, cutoffNorm, hannWindow);      // ve h[n]
  const H = firFrequencyResponse(h, 2048);
  const magDb = magnitudeToDb(dftMagnitude(H), -80);                 // ve |H(f)| theo dB
  const groupDelay = firGroupDelay(numTaps);                         // luon (numTaps-1)/2
  return { h, magDb, groupDelay };
}

Tóm lược

  • ✅ FIR = $y[n] = x[n]*h[n]$ với $h[n]$ hữu hạn — trung bình trượt là 1 trường hợp đặc biệt (verified: giảm phương sai tín hiệu nhiễu rõ rệt), nhưng đáp ứng tần số kiểu sinc lởm chởm, chặn dải kém.
  • ✅ Low-pass lý tưởng = gạch chữ nhật tần số → sinc vô hạn không nhân quả. Thực dụng: cắt + dịch + cửa sổ (Bài 7) = windowed-sinc.
  • ✅ Verified: đối xứng $h[i]=h[N{-}1{-}i]$ sai số <$10^{-9}$ → linear phase → group delay HẰNG SỐ $(N{-}1)/2$ mẫu cho MỌI tần số — dạng sóng không méo, chỉ trễ.
  • ✅ Verified: DC≈1, cutoff≈0,5 (-6dB), dải chặn <0,001 — đúng hình dạng windowed-sinc lý thuyết.
  • ✅ High-pass (spectral inversion) và band-pass (dịch tần) đều verified, vẫn giữ linear phase.
  • ✅ Pitfall: filter càng gắt càng dài → group delay càng lớn — gieo mầm IIR (Bài 11).

Trắc nghiệm ôn tập

Câu 1

Vì sao low-pass "lý tưởng" (gạch chữ nhật trong miền tần số) không thể cài đặt trực tiếp?

Câu 2

Verified: h[i] = h[N-1-i] (đối xứng hoàn hảo) trong windowed-sinc dẫn tới tính chất nào?

Câu 3

Cách thiết kế 1 filter high-pass bằng "spectral inversion" từ 1 low-pass đã có là gì?

Câu 4

Vì sao thiết kế 1 filter FIR có dải chuyển tiếp CÀNG DỐC (chặn dải "gắt" hơn) lại là một đánh đổi?

Tải file code thực hành minh họa bài học

File JavaScript DSPJS (dsp-core.js) — thư viện DSP tự viết dùng xuyên suốt cả 15 bài, Bài 9 vừa thêm firLowpassDesign()/firHighpassDesign()/firBandpassDesign() (windowed-sinc, tái sử dụng cửa sổ Bài 7), kèm self-test đối chiếu đúng mọi hành vi trong bài (chạy node dsp-core.js, không cần cài thêm gì):

Tải về dsp-core.js

📖 Tài liệu tham khảo

Bài viết liên quan trong series

Bài 8: STFT & Spectrogram Bài 10: Z-transform & mặt phẳng z Quay lại Lộ trình Series Xử Lý Tín Hiệu Số

Bình luận