Mở đầu: 2 pole làm việc của hàng chục tap
Bài 9 xây FIR bằng windowed-sinc — mạnh, an toàn về pha, nhưng cái giá là SỐ LƯỢNG: filter càng gắt càng cần nhiều tap, càng nhiều tap càng trễ. Bài 10 mở khoá công cụ pole/zero để MÔ TẢ hệ đệ quy — bài này dùng chính công cụ đó để XÂY một loại filter hoàn toàn khác triết lý: IIR (Infinite Impulse Response), nơi feedback cho phép chỉ vài hệ số làm việc của hàng chục, hàng trăm tap FIR.
Trọng tâm bài là biquad — khối filter bậc 2 (2 pole, 2 zero) là viên gạch CHUẨN CÔNG NGHIỆP của gần như mọi phần mềm âm thanh thật (plugin EQ, CMSIS-DSP, Web Audio API). Ta sẽ implement đúng dạng tối ưu Direct Form II Transposed, nạp công thức RBJ Audio EQ Cookbook để biến 3 con số dễ hiểu bằng tai — tần số, độ nhọn, độ lợi — thành hệ số filter, rồi chạm vào cạm bẫy kinh điển nhất của IIR: lượng tử hoá hệ số có thể đẩy một pole ổn định trên giấy trượt ra ngoài vòng tròn đơn vị.
1. IIR — sức mạnh của feedback
Một hệ FIR có đáp ứng xung HỮU HẠN: $y[n]$ chỉ phụ thuộc một cửa sổ hữu hạn các mẫu $x$ trong quá khứ. Một hệ IIR thêm feedback — $y[n]$ phụ thuộc CẢ các mẫu $y$ trong quá khứ — nên đáp ứng xung về lý thuyết kéo dài VÔ HẠN dù phương trình sai phân chỉ có vài hệ số. Đây chính là hệ đệ quy mà Bài 10 đã mổ xẻ bằng pole/zero: một cặp pole đặt đúng chỗ tạo ra cả một "đuôi" đáp ứng xung tắt dần mà FIR phải liệt kê tường minh từng mẫu.
Cái giá đổi lại: IIR có pha PHI TUYẾN (không còn "quà tặng đối xứng" của Bài 9) và mang rủi ro mất ổn định thật sự (pole có thể trượt ra ngoài vòng tròn — Mục 4 sẽ thấy đúng chuyện này xảy ra chỉ vì làm tròn số). FIR luôn ổn định vô điều kiện (không pole nào ngoài gốc toạ độ); IIR thì không có sự đảm bảo miễn phí đó.
| Tiêu chí | FIR (Bài 9) | IIR / Biquad (bài này) |
|---|---|---|
| Đáp ứng xung | Hữu hạn, tường minh | Vô hạn, nén trong vài hệ số |
| Pha | Tuyến tính (đối xứng) — không méo dạng sóng | Phi tuyến — trễ khác nhau theo tần số |
| Ổn định | Luôn ổn định vô điều kiện | Phải kiểm tra pole — có thể mất ổn định |
| Chi phí đạt độ dốc dải chuyển tiếp gắt | Cao — nhiều tap, verified: cần ~18-21 tap để chặn -32dB | Thấp — 1 biquad (2 pole) đã chặn -32dB |
| Dùng khi nào | Mastering, đo lường, filter bank — nơi pha là vàng | EQ realtime, tiết kiệm CPU/bộ nhớ nhúng |
Con số verified: một biquad lowpass 2 pole (cutoff 1000Hz, $Q=0,707$, $f_s=48000$Hz) chặn đúng -32,03dB tại 6000Hz. Để một FIR windowed-sinc (Hamming, cùng cutoff) đạt mức chặn tương đương, cần khoảng 18-21 tap (15 tap mới chặn -26dB, 21 tap đã vọt lên -53dB) — 2 hệ số feedback làm việc của hàng chục hệ số FIR liệt kê tường minh.
2. Biquad — viên gạch chuẩn công nghiệp
"Biquad" (viết tắt bi-quadratic) là filter bậc 2: 2 pole, 2 zero, phương trình sai phân $y[n] =
b_0 x[n] + b_1 x[n-1] + b_2 x[n-2] - a_1 y[n-1] - a_2 y[n-2]$ (đã chuẩn hoá $a_0=1$). Có 3 cách CÀI ĐẶT
toán học TƯƠNG ĐƯƠNG của cùng phương trình này — Direct Form I (giữ riêng lịch sử $x$ và
$y$, 4 biến trạng thái), Direct Form II (gộp thành 2 biến trạng thái chung nhưng dễ tràn
số ở điểm giữa), và Direct Form II Transposed (DF2T) — đảo sơ đồ khối, chỉ 2 biến trạng
thái $z_1, z_2$ và ÍT nhạy hơn với sai số làm tròn dấu phẩy động. DF2T là dạng mà gần như mọi thư viện
audio thật (CMSIS-DSP, JUCE, Web Audio BiquadFilterNode) dùng mặc định.
// Direct Form II Transposed - chi 2 bien trang thai z1, z2 (khong luu rieng
// lich su x[] va y[]) nen tich luy sai so lam tron IT hon Direct Form I/II.
function biquadDF2T(x, coeffs) {
const { b0, b1, b2, a1, a2 } = coeffs; // da chuan hoa a0=1
let z1 = 0, z2 = 0;
const y = new Array(x.length);
for (let n = 0; n < x.length; n++) {
const xn = x[n];
const yn = b0 * xn + z1;
z1 = b1 * xn - a1 * yn + z2;
z2 = b2 * xn - a2 * yn;
y[n] = yn;
}
return y;
}
// Verified: bDF2T(impulse) khop TUYET DOI voi iirFilterDirect (Bai 10) cho
// cung he so - sai khac toi da giua 2 dang duoi 1e-12 (chi la 2 CACH TINH
// khac nhau cua CUNG mot phuong trinh sai phan).
3. RBJ Audio EQ Cookbook: từ (f0, Q, gain) ra hệ số
Tự tay suy ra hệ số biquad từ phân tích analog → bilinear transform mỗi lần cần 1 EQ mới là việc không ai làm lại — Robert Bristow-Johnson đã hệ thống hoá thành "Audio EQ Cookbook", bộ công thức đóng gói sẵn cho 7 loại filter, chỉ cần nạp 3 tham số dễ hiểu bằng tai: $f_0$ (tần số trung tâm/cắt), $Q$ (độ nhọn cộng hưởng), và $gainDb$ (chỉ dùng cho peaking/shelf).
// RBJ Audio EQ Cookbook - da ap san bilinear transform, chi can nap (f0, Q, gainDb)
function biquadCoeffsRBJ(type, f0, fs, Q, gainDb = 0) {
const A = Math.pow(10, gainDb / 40);
const w0 = (2 * Math.PI * f0) / fs;
const alpha = Math.sin(w0) / (2 * Q);
// ... 7 nhanh cong thuc (lowpass/highpass/bandpass/notch/peaking/lowshelf/highshelf) ...
return { b0: b0 / a0, b1: b1 / a0, b2: b2 / a0, a1: a1 / a0, a2: a2 / a0 };
}
// Verified thuc: peaking +6dB tai f0 cho DUNG +6.000000dB (khong sai so tich luy)
// lowshelf +12dB: DC = +12dB, Nyquist = 0dB (dai cao khong doi) - dung nhu ten goi
Verify bằng self-test — không giả định, tính THẬT từ đa thức $H(e^{j\omega})$: filter
peaking boost +6dB tại $f_0$ cho ĐÚNG +6,000000dB tại tần số đó; filter
lowshelf +12dB cho DC = +12dB nhưng Nyquist = 0dB (giữ nguyên dải cao, đúng nghĩa "shelf" —
bậc thang chỉ nâng MỘT phía); notch khoét $|H|<10^{-9}$ ngay tại $f_0$ — gần như 0 tuyệt đối.
bandpass tại $f_0=1000$Hz: $Q=0,5$ cho băng thông 1970Hz (rất rộng, êm
tai), $Q=1$ cho 990Hz, $Q=5$ cho 190Hz, và $Q=10$ cho chỉ
95Hz (rất hẹp, gần như một "kim" tần số — dễ nghe ra tiếng "leng keng" cộng hưởng). $Q$
càng cao, pole càng tiến gần vòng tròn đơn vị (Bài 10) — đây chính là hạt
giống của cạm bẫy ở Mục 4.
4. Filter bậc cao = cascade biquad
Cần độ dốc gắt hơn 1 biquad (12dB/octave với $Q=0,707$)? KHÔNG dùng một đa thức bậc cao duy nhất — nối tiếp (cascade) nhiều biquad bậc 2. Verified: cascade 2 biquad lowpass giống hệt nhau nhân độ chặn theo dB — biquad đơn chặn -32,03dB tại 6000Hz, 2 biquad nối tiếp chặn đúng 2 × -32,03 = -64,06dB (dB CỘNG DỒN vì $|H_1 \cdot H_2| \to 20\log|H_1|+20\log|H_2|$). Butterworth bậc 4 thực chiến = đúng 2 biquad với hệ số $Q$ khác nhau nối tiếp.
Lý do KHÔNG dùng 1 đa thức bậc cao duy nhất: hệ số của một đa thức bậc 4+ CỰC KỲ nhạy với sai số làm tròn — một thay đổi nhỏ ở 1 hệ số có thể dịch chuyển pole đi rất xa. Cascade các biquad bậc 2 độc lập giữ mỗi khối "khoẻ mạnh" riêng, sai số không khuếch đại chéo giữa các tầng.
peaking $Q=30$ tại $f_0=20500$Hz ($f_s=48000$Hz) có
pole full-precision $|pole| = 0,9963$ — ổn định, nhưng SÁT vòng tròn (đúng dự đoán Mục 3: $Q$ cao = pole
gần vòng tròn). Làm tròn hệ số $a_1, a_2$ xuống chỉ 1 chữ số thập phân (mô phỏng số học
độ chính xác thấp) đẩy pole thành ĐÚNG $|pole| = 1,0$ — mất ổn định. Đây chính xác là
pitfall đã hé lộ ở Bài 10 ("pole gần vòng tròn ổn định trên giấy, nguy
hiểm thực tế"), và là lý do Bài 14 phải bàn tới Q15 fixed-point cẩn
trọng: MCU không FPU buộc dùng số học độ chính xác thấp, filter $Q$ cao là ứng viên "nổ" đầu tiên.
5. Thực hành: Biquad Cookbook Playground
Chọn loại filter, kéo $f_0$/$Q$/gain — pole/zero di chuyển live trên mặt phẳng z (tái dùng trực quan
flagship Bài 10), đáp ứng tần số vẽ lại tức thì, và "Nghe" lọc một đoạn
nhiễu trắng bằng CHÍNH bộ lọc đang cấu hình qua biquadDF2T() thật. Kéo $Q$ lên cao và $f_0$
gần Nyquist để thấy trực quan pole tiến sát vòng tròn — đúng hiện tượng đã verify ở Mục 4:
Đáp ứng tần số |H(f)| (dB):
Sóng vuông: input (mờ) vs qua IIR (đậm) — méo dạng, đối chứng FIR Bài 9:
Kéo Q lên cao (≥15) và f0 gần Nyquist (≥20000Hz) — pole trên mặt phẳng z tiến sát viền vòng tròn đơn vị, đúng hiện tượng pitfall Mục 4.
import { biquadCoeffsRBJ, biquadPoles, biquadZeros, biquadDF2T, freqRespFromPZ, whiteNoise, square } from './dsp-core.js';
function updateFilter(type, f0, fs, Q, gainDb) {
const coeffs = biquadCoeffsRBJ(type, f0, fs, Q, gainDb);
const poles = biquadPoles(coeffs); // doc truc tiep tu he so - Bai 10 tai dung
const zeros = biquadZeros(coeffs);
const freqResp = freqRespFromPZ(zeros, poles, coeffs.b0, 200); // hinh hoc Bai 10
const noise = Array.from({ length: 4000 }, (_, n) => whiteNoise(n, 0.3, 3));
const filtered = whiteNoiseFiltered(noise, coeffs); // qua DF2T THAT
const sq = Array.from({ length: 200 }, (_, n) => square(n, 1 / 40, 1, 1, 0));
const sqFiltered = biquadDF2T(sq, coeffs); // meo dang - doi chung FIR Bai 9 (khong meo)
return { coeffs, poles, zeros, freqResp, filtered, sq, sqFiltered };
}
function whiteNoiseFiltered(x, coeffs) {
return biquadDF2T(x, coeffs);
}
Tóm lược
- ✅ IIR dùng feedback nén đáp ứng xung vô hạn vào vài hệ số — verified: 1 biquad (2 pole) chặn -32,03dB tại 6kHz, FIR cần ~18-21 tap mới theo kịp; cái giá là pha phi tuyến + rủi ro mất ổn định.
-
✅ Direct Form II Transposed (DF2T) — chỉ 2 biến trạng thái, ít nhạy sai số làm tròn hơn Direct Form
I/II — verified khớp TUYỆT ĐỐI với
iirFilterDirect()của Bài 10 (sai khác <1e-12). - ✅ RBJ Audio EQ Cookbook: 7 công thức từ $(f_0, Q, gainDb)$ — verified peaking +6dB cho đúng +6dB tại $f_0$, lowshelf +12dB giữ nguyên Nyquist 0dB; $Q$ điều khiển độ nhọn — verified $Q=10$ cho băng thông chỉ 95Hz so với $Q=0,5$ cho 1970Hz.
- ✅ Filter bậc cao = cascade biquad bậc 2 — verified 2 tầng chặn gấp đôi dB (-64,06dB) so với 1 tầng.
- ✅ Pitfall: lượng tử hệ số đẩy pole $Q$ cao trượt ra ngoài vòng tròn — verified pole $|p|=0,9963$ (ổn định) → $|p|=1,0$ (mất ổn định) chỉ vì làm tròn 1 chữ số thập phân.
Trắc nghiệm ôn tập
Câu 1
Vì sao 1 biquad (2 pole) có thể đạt độ chặn dải mà FIR cần hàng chục tap?
Câu 2
Direct Form II Transposed (DF2T) thắng Direct Form I ở điểm nào?
Câu 3
Verified: Q=10 cho băng thông -3dB chỉ 95Hz, Q=0,5 cho 1970Hz. Q điều khiển điều gì?
Câu 4
Verified: pole $Q=30$ full-precision có $|pole|=0,9963$ (ổn định), làm tròn hệ số xuống 1 chữ số thập phân đẩy $|pole|$ thành đúng $1,0$ (mất ổn định). Bài học rút ra là gì?
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 11 vừa thêm biquadCoeffsRBJ() (7 loại RBJ cookbook), biquadDF2T(),
biquadPoles()/biquadZeros(), 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ì):
Bình luận