Mở đầu: âm thanh trong tai bạn không phải là dãy số trong máy tính

Giọng nói phát ra từ loa là một sóng áp suất không khí biến thiên liên tục — tại mọi khoảnh khắc, dù nhỏ tới đâu, đều có một giá trị áp suất xác định. Nhưng file MP3 lưu trên máy tính chỉ chứa một danh sách hữu hạn các con số. Ở giữa hai thế giới đó — sóng liên tục ngoài đời và dãy số rời rạc trong máy — là toàn bộ nội dung của Xử Lý Tín Hiệu Số (DSP). Bài đầu tiên này xây nền tảng khái niệm và cú pháp toán học sẽ dùng xuyên suốt 14 bài còn lại: tín hiệu là gì, tín hiệu rời rạc khác tín hiệu liên tục ra sao, và những "khối Lego" cơ bản nhất mà mọi tín hiệu phức tạp đều được ghép từ đó.

Khác với hầu hết tài liệu DSP dừng lại ở công thức, series này bắt bạn tự viết từng tín hiệu bằng JavaScript thuần ngay từ bài đầu — không gọi hàm có sẵn nào, không có hộp đen nào. Cuối bài, bạn sẽ nghe được chính xác một chu kỳ sine, một cú chirp quét tần số, và một dải nhiễu trắng — tất cả do chính công thức bạn vừa học tạo ra.


1. Phân loại tín hiệu

Trước khi chạm vào công thức, cần phân biệt rõ 2 trục phân loại độc lập với nhau:

  • Liên tục (continuous-time) vs rời rạc (discrete-time) — trục tính chất theo THỜI GIAN: một tín hiệu liên tục có giá trị tại MỌI thời điểm thực; một tín hiệu rời rạc chỉ tồn tại tại các mốc thời gian đếm được (thường cách đều nhau).
  • Tương tự (analog) vs số (digital) — trục tính chất theo BIÊN ĐỘ: tín hiệu tương tự có biên độ nhận vô số giá trị liên tục; tín hiệu số có biên độ bị làm tròn về một tập hữu hạn mức (sẽ mổ xẻ kỹ ở Bài 3 — lượng tử hoá).

Hai trục này ĐỘC LẬP với nhau — ghép lại cho 4 tổ hợp: giọng nói ngoài đời là liên tục + tương tự; một file WAV trong máy tính là rời rạc + số; tín hiệu ra khỏi micro trước khi vào ADC là liên tục + tương tự nhưng đã được khuếch đại điện tử; và (ít gặp hơn) rời rạc + tương tự tồn tại trong một số mạch chuyển mạch tụ điện (switched-capacitor) chuyên dụng. Bài này — và gần như toàn bộ series — tập trung vào góc phần tư rời rạc + số, vì đó là thứ máy tính thực sự xử lý được.

2. x(t) vs x[n]: từ hàm liên tục đến dãy số

Quy ước ký hiệu chuẩn của cả ngành: tín hiệu liên tục viết là $x(t)$ (ngoặc tròn, biến $t$ là thời gian thực — một số thực bất kỳ); tín hiệu rời rạc viết là $x[n]$ (ngoặc vuông, biến $n$ là chỉ số MẪU — một số nguyên). Sự khác biệt ký hiệu này không phải hình thức — nó nhắc bạn nhớ $n$ chỉ có thể là 0, 1, 2, 3... không có "giữa mẫu thứ 2 và thứ 3".

Khi lấy mẫu $x(t)$ tại các thời điểm cách đều nhau $T$ giây (chu kỳ lấy mẫu), ta được:

$$x[n] = x(nT)$$

Nghịch đảo của $T$ là tần số lấy mẫu $f_s = 1/T$ (đơn vị Hz — số mẫu lấy mỗi giây). Đây là hằng số quan trọng nhất của cả series — mọi công thức từ đây trở đi đều phụ thuộc vào nó, và Bài 2 sẽ dành trọn vẹn cho câu hỏi trung tâm: lấy mẫu thưa tới đâu thì bắt đầu MẤT thông tin so với $x(t)$ gốc?

3. Các tín hiệu cơ bản

6 "khối Lego" sau xuất hiện lặp lại trong toàn bộ series — verify từng công thức bằng số thật trước khi dùng:

  • Xung đơn vị (unit impulse) $\delta[n - n_0]$: đúng 1 tại $n=n_0$, còn lại 0 — verify: unitImpulse(0) = 1, unitImpulse(5) = 0. Đây là tín hiệu "nguyên tử" nhất của DSP — Bài 4 sẽ cho thấy đáp ứng của một hệ với xung đơn vị đặc trưng hoá TRỌN VẸN hệ đó.
  • Bậc thang đơn vị (unit step) $u[n - n_0]$: 0 trước $n_0$, 1 từ $n_0$ trở đi — verify: unitStep(-1) = 0, unitStep(0) = 1.
  • Sine: $x[n] = A\sin(2\pi f n/f_s + \varphi)$ — tín hiệu cơ bản nhất của miền tần số, vì Bài 5 trở đi sẽ phân tích MỌI tín hiệu thành tổng của các sine như thế này (biến đổi Fourier).
  • Sóng vuông: lấy đúng dấu của sine cùng tần số — nghe "gắt" hơn hẳn sine vì chứa nhiều hoạ âm (sẽ thấy rõ trên phổ ở Bài 5–6).
  • Chirp: tần số tức thời trượt đều từ $f_0$ đến $f_1$ trong một khoảng thời gian — dùng để "quét" toàn dải tần trong một tín hiệu duy nhất, sẽ là công cụ chính của Aliasing Stroboscope ở Bài 2.
  • Nhiễu trắng: biên độ ngẫu nhiên tại mọi mẫu, năng lượng trải đều trên mọi tần số. Bản tự viết ở đây dùng một hàm băm tất định (deterministic) thay vì Math.random(), để cùng một seed luôn cho ra đúng cùng một chuỗi số — cần thiết để self-test và demo trên trình duyệt luôn khớp nhau tuyệt đối.

4. Năng lượng vs công suất

Hai đại lượng đo "độ lớn" của một tín hiệu, dùng cho hai loại tín hiệu khác nhau. Năng lượng (energy) — tổng bình phương biên độ trên toàn bộ tín hiệu:

$$E = \sum_{n} |x[n]|^2$$

Công thức này chỉ có ý nghĩa khi $E$ HỮU HẠN — đúng cho tín hiệu tắt dần hoặc có chiều dài giới hạn (như một xung đơn vị). Với tín hiệu tuần hoàn hoặc kéo dài vô hạn (như một sine không bao giờ tắt), tổng đó tiến ra vô cực — lúc này phải đo công suất (power), trung bình bình phương biên độ thay vì tổng:

$$P = \frac{1}{N}\sum_{n=0}^{N-1} |x[n]|^2$$

Verify bằng số thật: lấy đúng 1 chu kỳ trọn vẹn của sine biên độ 1 (8 mẫu ở 1000Hz/8000Hz), công suất trung bình tính ra đúng 0,5 — khớp chính xác công thức lý thuyết $P = A^2/2$ với biên độ $A=1$. Con số 0,5 này sẽ quay lại ở Bài 3 khi tính SNR (tỷ số tín hiệu trên nhiễu) và ở Series Web Audio khi đo mức decibel.

5. Chu kỳ trong miền rời rạc

Một sine liên tục $\sin(2\pi f t)$ LUÔN tuần hoàn với chu kỳ $1/f$ giây, không có ngoại lệ. Nhưng sine rời rạc $x[n] = \sin(2\pi f n/f_s)$ chỉ tuần hoàn (có chu kỳ hữu hạn tính theo số mẫu $n$) khi tỷ lệ $f/f_s$ là một số hữu tỷ. Với $f$ và $f_s$ đều là số nguyên (luôn hữu tỷ), chu kỳ rời rạc — số mẫu nhỏ nhất để pha lặp lại đúng — tính bằng:

$$N = \frac{f_s}{\gcd(f, f_s)}$$

trong đó $\gcd$ là ước số chung lớn nhất.

⚠️ Verify: hai tần số khác nhau có thể cho CÙNG một chu kỳ rời rạc
Tính chu kỳ cho 1000Hz3000Hz trên cùng tần số lấy mẫu 8000Hz: cả hai đều cho ra đúng 8 mẫu — dù bản thân hai tần số khác nhau tới 3 lần! Đây không phải trùng hợp: đó chính là dấu hiệu đầu tiên của hiện tượng gập phổ (aliasing) mà Bài 2 sẽ mổ xẻ toàn bộ — hai tín hiệu tưởng chừng khác nhau hoàn toàn, sau khi lấy mẫu, có thể trở nên KHÔNG THỂ PHÂN BIỆT được.

Một ví dụ thực tế hơn: nốt La chuẩn (A440, 440Hz) trên tần số lấy mẫu CD/audio phổ biến 44.100Hz có chu kỳ rời rạc đúng 2205 mẫu — verify bằng chính công thức trên ($44100/\gcd(440,44100) = 44100/20 = 2205$).

6. Thực hành: Signal Generator Playground

Demo dưới đây chạy đúng 6 hàm sinh tín hiệu vừa học, tự viết trong dsp-core.js — chọn một loại, xem dạng sóng, và bấm nghe bằng chính tai bạn:

🎛️ Signal Generator Playground — DSPJS thật
Bấm để tạo AudioContext (sau cử chỉ người dùng).
Đang tải…

Đồ thị hiển thị 300 mẫu đầu tiên. Năng lượng/công suất tính trên toàn bộ 0,5 giây (22.050 mẫu ở 44.100Hz) — quan sát: xung đơn vị có năng lượng hữu hạn rất nhỏ (chỉ 1 mẫu khác 0), còn sine/vuông có công suất ổn định gần 0,5 vì kéo dài liên tục.

signal_demo.js (đúng logic đang chạy ở tab Xem trước)
import { generateSignal, signalEnergy, signalPower, discreteSinePeriod } from './dsp-core.js';

const sampleRateHz = 44100;
const numSamples = Math.round(sampleRateHz * 0.5); // 0,5 giay

const sine = generateSignal('sine', numSamples, { freqHz: 440, sampleRateHz, amplitude: 0.5 });
console.log('Công suất sine 440Hz:', signalPower(sine));       // ~0.125 (A=0.5 -> A^2/2)
console.log('Chu kỳ rời rạc:', discreteSinePeriod(440, sampleRateHz)); // 2205 mau

const impulse = generateSignal('impulse', numSamples, { n0: 100 });
console.log('Năng lượng xung đơn vị:', signalEnergy(impulse));  // 1 (chi 1 mau khac 0)

// Phat am thanh that (AudioContext tao SAU cu chi nguoi dung - autoplay policy):
const ctx = new AudioContext();
const buffer = ctx.createBuffer(1, sine.length, sampleRateHz);
buffer.copyToChannel(new Float32Array(sine), 0);
const src = ctx.createBufferSource();
src.buffer = buffer;
src.connect(ctx.destination);
src.start();

Tóm lược

  • ✅ Hai trục phân loại độc lập: liên tục/rời rạc (thời gian) và tương tự/số (biên độ) — series tập trung vào rời rạc + số, nơi máy tính thực sự xử lý.
  • ✅ $x(t)$ (hàm liên tục lý tưởng) vs $x[n] = x(nT)$ (dãy số máy tính lưu được) — tần số lấy mẫu $f_s = 1/T$ là hằng số quan trọng nhất của cả series.
  • ✅ 6 tín hiệu cơ bản tự viết: xung đơn vị, bậc thang, sine, sóng vuông, chirp, nhiễu trắng tất định (không dùng Math.random()).
  • ✅ Năng lượng ($\sum |x[n]|^2$, cho tín hiệu hữu hạn) vs công suất ($\tfrac{1}{N}\sum|x[n]|^2$, cho tín hiệu tuần hoàn/vô hạn) — verified: 1 chu kỳ sine trọn vẹn cho công suất đúng 0,5.
  • ✅ Chu kỳ rời rạc $N = f_s/\gcd(f,f_s)$ — verified: 1000Hz và 3000Hz @ 8000Hz cho CÙNG chu kỳ 8 mẫu, hé lộ gập phổ (Bài 2).

Trắc nghiệm ôn tập

Câu 1

Ký hiệu $x(t)$ và $x[n]$ khác nhau ở điều gì quan trọng nhất?

Câu 2

Vì sao cần phân biệt năng lượng (energy) và công suất (power) thay vì chỉ dùng một đại lượng duy nhất?

Câu 3

Verified: sine 1000Hz và sine 3000Hz trên CÙNG tần số lấy mẫu 8000Hz cho ra ĐÚNG cùng một chu kỳ rời rạc 8 mẫu. Điều này có ý nghĩa gì?

Câu 4

Vì sao nhiễu trắng trong DSPJS được tạo bằng một hàm băm tất định (deterministic) thay vì Math.random()?

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

File JavaScript DSPJS — thư viện DSP mini dùng xuyên suốt cả 15 bài, Bài 1 vừa thêm bộ sinh tín hiệu cơ bản (unitImpulse, unitStep, sine, square, chirp, whiteNoise), tiện ích năng lượng/công suất, và chu kỳ rời rạc, 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 2: Lấy mẫu, Nyquist & Aliasing Quay lại Lộ trình Series Xử Lý Tín Hiệu Số

Bình luận