Mở đầu: 14 khối rời, giờ ghép thành 1 dây chuyền
14 bài trước xây từng KHỐI riêng lẻ — cửa sổ, FFT, FIR, biquad, resampler, tuner, Q15. Bài capstone này KHÔNG dạy thuật toán DSP mới — nó dạy KIẾN TRÚC: cách ghép nhiều khối xử lý thành một dây chuyền (pipeline) hoàn chỉnh, đúng cách mọi phần mềm âm thanh thật (DAW, plugin, loa thông minh) tổ chức code. Trạm Âm Thanh DSP hôm nay: noise gate (Bài 11 + khái niệm FSM) → EQ 5 băng (biquad cascade, Bài 11) → soft-limiter (đối chứng Bài 3) → loa, với đồng hồ đo mức đúng chuẩn kỹ thuật.
1. Kiến trúc DSP Station
Đồ thị xử lý: nguồn (mic/nhạc) → noise gate → EQ 5 băng →
soft-limiter → loa, kèm 1 nhánh PHÂN TÍCH chạy song song (không nằm trên đường tín hiệu
chính) tái dùng spectrogram của Bài 8 và tuner của
Bài 13. Kiến trúc code: MỖI khối là một hàm/đối tượng nhận
process(block) — nhận 1 mảng mẫu, trả về 1 mảng mẫu đã xử lý — để dây chuyền có thể GHÉP bất
kỳ thứ tự nào, bật/tắt từng khối độc lập mà không phá vỡ khối khác:
import { noiseGate, cascadeBiquads, softClip } from './dsp-core.js';
// Moi khoi la 1 HAM process(block) -> block moi - de GHEP, de BAT/TAT rieng
function buildChain({ gateOn, eqOn, limiterOn, eqBands, fs }) {
return function process(block) {
let y = block;
if (gateOn) y = noiseGate(y, fs, -35, 5, 150, 6);
if (eqOn) y = cascadeBiquads(y, eqBands);
if (limiterOn) y = y.map((v) => softClip(v, 0.9));
return y;
};
}
// Verified: cascadeBiquads() khop TUYET DOI voi goi biquadDF2T() lien tiep
// thu cong (Bai 11) - EQ 5 bang chi la 5 lan goi noi tiep, khong gi moi.
2. EQ 5 băng từ biquad cascade
EQ 5 băng chuẩn: low-shelf (điều khiển "đáy"), 3× peaking (điều khiển
từng vùng tần số giữa), high-shelf (điều khiển "đỉnh") — mỗi băng là MỘT biquad từ RBJ
cookbook (Bài 11), nối tiếp bằng cascadeBiquads(). Đáp ứng tần
số TỔNG của cả chuỗi = TÍCH các $H(z)$ từng băng (đọc theo hình học của
Bài 10) — hay cộng dồn theo dB, vì $20\log|H_1 H_2| = 20\log|H_1| +
20\log|H_2|$.
Verified bằng số thật: preset bass-boost (low-shelf +6dB @120Hz) cho tổng khuếch đại đúng +5,73dB tại 80Hz nhưng gần như 0dB tại 1kHz — đúng ý nghĩa "shelf": nâng MỘT VÙNG, giữ nguyên phần còn lại. Mỗi preset (bass-boost/vocal/hall) chỉ là MỘT bộ hệ số $(f_0, Q, gainDb)$ khác nhau cho 5 băng — không cần đổi code, chỉ đổi THAM SỐ.
| Preset | Low-shelf | 3× Peaking | High-shelf | Vì sao hình dạng này |
|---|---|---|---|---|
| Bass-boost | +6dB @120Hz | 0dB | 0dB | Chỉ nâng đáy — nhạc điện tử/hip-hop cần lực trầm mà không đục âm |
| Vocal | -3dB @150Hz (cắt bùn) | +4dB @3kHz (presence) | +2dB @8kHz (độ sáng) | Cắt vùng "bùn" thấp, nâng vùng "hiện diện" giọng hát dễ nghe rõ lời |
| Hall | +1dB @100Hz | -2dB @1kHz (nhẹ) | -3dB @10kHz | Mô phỏng cách phòng lớn hấp thụ tần số cao, dội nhẹ tần số thấp |
3. Noise gate: envelope follower & FSM hysteresis
Noise gate quyết định "im lặng thật" (nên câm) hay "có tín hiệu" (nên mở) bằng cách bám biên độ qua envelope follower (bộ lọc 1 cực bất đối xứng attack/release), rồi so với ngưỡng qua một FSM 2 trạng thái có hysteresis: mở khi vượt ngưỡng mở, chỉ đóng khi tụt XUỐNG DƯỚI một ngưỡng đóng THẤP HƠN — tránh "nhấp nháy" mở/đóng liên tục quanh một ngưỡng duy nhất (đúng tinh thần FSM chống dội đã học ở phần cứng nhúng).
function noiseGate(x, fs, thresholdDb, attackMs, releaseMs, hysteresisDb) {
const decisionEnv = envelopeFollower(x, fs, 0.5, 5); // envelope QUYET DINH, rieng biet
const openThresh = thresholdDb, closeThresh = thresholdDb - hysteresisDb;
let isOpen = false;
const targetGain = new Array(x.length);
for (let n = 0; n < x.length; n++) {
const envDb = amplitudeToDbfs(decisionEnv[n] + 1e-9);
if (!isOpen && envDb > openThresh) isOpen = true; // FSM: mo
else if (isOpen && envDb < closeThresh) isOpen = false; // FSM: dong (nguong THAP hon)
targetGain[n] = isOpen ? 1 : 0;
}
const smoothGain = envelopeFollower(targetGain, fs, attackMs, releaseMs); // "toc do" nghe duoc
return x.map((v, n) => v * smoothGain[n]);
}
4. Gain staging & chống clip xuyên chuỗi
Mỗi khối trong chuỗi có thể LÀM TO tín hiệu (EQ boost, ví dụ) — nếu không theo dõi headroom, tổng tín hiệu
tới khối SAU có thể vượt $\pm 1,0$ và bị clip trước khi tới softClip(). Chuỗi này dùng
soft-limiter ($\tanh$, Bài 11 không có nhưng
Bài 3 đã có hardClip() để đối chứng) thay vì clip cứng ở khâu
cuối cùng.
Verified: trên tín hiệu overdrive mạnh, hardClip() khiến hơn 30% số mẫu
FLATLINE CHÍNH XÁC tại biên $\pm 1,0$ — đạo hàm gián đoạn, tạo góc nhọn trong dạng sóng (nguồn gốc của méo
hài bậc cao, khó nghe); softClip() ngược lại KHÔNG MẪU NÀO chạm đúng ngưỡng (0/4096) — chỉ
tiệm cận, đường cong luôn liên tục mượt.
function softClip(x, threshold = 1) {
return threshold * Math.tanh(x / threshold); // MUOT, khong bao gio "gay" dot ngot
}
// Do muc dung chuan: Peak (bien do tuc thoi) va RMS (cong suat trung binh, gan voi
// do to CAM NHAN duoc hon Peak) - tai dung amplitudeToDbfs()/signalPower() cua Bai 1, 3
function meterDb(x) {
const peak = Math.max(...x.map(Math.abs));
const rms = Math.sqrt(signalPower(x));
return { peakDb: amplitudeToDbfs(peak), rmsDb: amplitudeToDbfs(rms) };
}
| Đại lượng | Ý nghĩa | Dùng khi nào |
|---|---|---|
| Peak (dBFS) | Biên độ tuyệt đối lớn nhất — amplitudeToDbfs() của Bài 3 |
Canh headroom, tránh clip tức thời |
| RMS (dBFS) | Căn bậc 2 công suất trung bình — signalPower() của Bài 1 |
Ước lượng độ to CẢM NHẬN được (tai nhạy công suất trung bình hơn đỉnh tức thời) |
5. Thực hành: DSP Station hoàn chỉnh
Chọn nguồn (tín hiệu test dựng sẵn hoặc ghi 2 giây từ mic thật), bật/tắt từng khối (gate/EQ/limiter), chọn preset EQ — nghe kết quả VÀ xem đồng hồ Peak/RMS trước/sau cùng đáp ứng tần số tổng của EQ đang chọn:
Đáp ứng tần số TỔNG của EQ (dB):
Tín hiệu test = 1 tông 300Hz + khoảng lặng + nhiễu nhỏ, để nghe rõ hiệu ứng gate. Tắt từng khối để so sánh A/B trực tiếp.
import { noiseGate, cascadeBiquads, softClip, biquadCoeffsRBJ, signalPower, amplitudeToDbfs } from './dsp-core.js';
const PRESETS = {
bass: [['lowshelf', 120, 0.9, 6], ['peaking', 500, 1, 0], ['peaking', 1500, 1, 0], ['peaking', 5000, 1, 0], ['highshelf', 8000, 0.9, 0]],
vocal: [['lowshelf', 150, 0.9, -3], ['peaking', 800, 1, 0], ['peaking', 3000, 1.2, 4], ['peaking', 5000, 1, 0], ['highshelf', 8000, 0.9, 2]],
hall: [['lowshelf', 100, 0.9, 1], ['peaking', 500, 1, 0], ['peaking', 1000, 1, -2], ['peaking', 4000, 1, 0], ['highshelf', 10000, 0.9, -3]],
};
function processChain(x, fs, { gateOn, eqOn, limiterOn, presetKey }) {
let y = x;
if (gateOn) y = noiseGate(y, fs, -35, 5, 150, 6);
if (eqOn) {
const bands = PRESETS[presetKey].map(([type, f0, Q, gainDb]) => biquadCoeffsRBJ(type, f0, fs, Q, gainDb));
y = cascadeBiquads(y, bands);
}
if (limiterOn) y = y.map((v) => softClip(v, 0.9));
return y;
}
function meterDb(x) {
const peak = Math.max(...x.map(Math.abs));
const rms = Math.sqrt(signalPower(x));
return { peakDb: amplitudeToDbfs(peak), rmsDb: amplitudeToDbfs(rms) };
}
Checklist tự đánh giá
Trước khi coi Series 14 hoàn thành, tự kiểm tra:
- ☑️ Giải thích được vì sao aliasing xảy ra và cách chống nó (Bài 2, 12).
- ☑️ Tự tay viết được FFT lặp (không đệ quy) và biết khi nào dùng DFT trực tiếp là đủ (Bài 6).
- ☑️ Phân biệt được FIR và IIR, biết khi nào chọn loại nào (Bài 9, 11).
- ☑️ Đọc được đáp ứng tần số bằng HÌNH HỌC từ vị trí pole/zero (Bài 10).
- ☑️ Biết chính xác vì sao Q15 tồn tại và cái giá của nó (Bài 14).
- ☑️ Ghép được nhiều khối DSP thành 1 dây chuyền có thể bật/tắt độc lập (Bài 15 — bài này).
Bản đồ học tiếp sau Series 14: adaptive filter (hệ số filter tự điều chỉnh theo tín hiệu, dùng trong khử tiếng vọng/khử ồn chủ động), wavelet (phân tích đa độ phân giải — thay STFT có cửa sổ cố định bằng cửa sổ đổi theo tần số), và audio ML — spectrogram của Bài 8 chính là input chuẩn cho CNN nhận diện giọng nói/âm nhạc (nối thẳng sang Series 12 — AI).
Trắc nghiệm ôn tập
Câu 1
Vì sao mỗi khối trong DSP Station được viết dạng hàm process(block) độc lập?
Câu 2
Verified: preset bass-boost cho +5,73dB tại 80Hz nhưng ~0dB tại 1kHz. Vì sao đúng nghĩa "shelf"?
Câu 3
Vì sao noise gate cần HYSTERESIS (ngưỡng mở khác ngưỡng đóng) thay vì chỉ 1 ngưỡng duy nhất?
Câu 4
Verified: hardClip() flatline chính xác tại biên hơn 30% số mẫu trên tín hiệu overdrive; softClip() không mẫu nào chạm đúng ngưỡng. Ý nghĩa thực tế 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 15 vừa thêm cascadeBiquads(), envelopeFollower(),
noiseGate(), softClip() — 34 hàm cộng dồn từ Bài 1, kèm self-test đối chiếu
đúng mọi hành vi trong toàn bộ series (chạy node dsp-core.js, không cần cài thêm gì):
Bình luận