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ị.


📚 Điều kiện tiên quyết
Bắt buộc: Bài 10 (pole/zero, mặt phẳng z, ổn định) và Bài 9 (khái niệm filter, FIR làm đối chứng).

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.

biquad_df2t.js (trích engine dsp-core.js)
// 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).
💡 Vì sao DF2T thắng trên dấu phẩy động
Direct Form I cần lưu 4 mẫu lịch sử ($x[n-1], x[n-2], y[n-1], y[n-2]$) — nhiều bộ nhớ, nhiều phép cộng riêng lẻ dễ tích luỹ sai số. DF2T gộp mọi đóng góp tương lai vào đúng 2 "bể chứa" $z_1, z_2$ được cập nhật MỘT LẦN mỗi mẫu — ít trạng thái hơn đồng nghĩa ít nguồn sai số làm tròn hơn, đặc biệt quan trọng khi chạy hàng nghìn mẫu/giây liên tục không dừng (audio realtime).

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_cookbook.js (trích engine dsp-core.js)
// 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.

🔬 Q là gì bằng tai — verified bằng số thật
$Q$ điều khiển ĐỘ NHỌN của cộng hưởng/dải thông — verified bằng cách đo băng thông -3dB thật của filter 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.
⚠️ Black-box: bilinear transform & frequency warping
Cookbook "âm thầm" đi qua một cây cầu toán học gọi là bilinear transform — ánh xạ mặt phẳng $s$ liên tục (analog) sang mặt phẳng $z$ rời rạc (Bài 10) bằng $s = \dfrac{2}{T}\dfrac{1-z^{-1}} {1+z^{-1}}$. Cây cầu này không tuyến tính: tần số analog và tần số digital tương ứng theo một đường cong (frequency warping), càng rõ khi $f_0$ tiến gần Nyquist. Phạm vi bài KHÔNG đi sâu suy ra công thức này — cookbook đã tính sẵn đúng phần bù, ta chỉ cần biết NÓ TỒN TẠI để không ngạc nhiên khi thấy $\sin(w_0)$ xuất hiện thay vì $f_0$ thô trong công thức.

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.

⚠️ Cạm bẫy: lượng tử hệ số đẩy pole trượt ra ngoài vòng tròn
Verified thật, không giả định: filter 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:

🎛️ Biquad Cookbook Playground — DSPJS thật

Đá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:

Đang khởi tạo…

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.

biquad_playground_demo.js (đúng logic đang chạy ở tab Xem trước)
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ì):

Tải về dsp-core.js

📖 Tài liệu tham khảo

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

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

Bình luận