Sau Bài 1 dựng đồ thị node cơ bản, bài này biến 1 tiếng "bíp" đơn
điệu thành âm sắc thật sự chơi được: 4 dạng sóng cơ bản, tinh chỉnh cao độ bằng detune,
và bao hình âm lượng ADSR — nền tảng của mọi synthesizer, từ analog cổ điển tới plugin hiện đại.
1. 4 Dạng Sóng Cơ Bản Và Nội Dung Họa Âm
OscillatorNode.type quyết định "màu sắc" âm thanh — không phải vì hình dạng sóng đẹp hay
xấu, mà vì mỗi dạng sóng chứa tập họa âm (harmonics) khác nhau chồng lên tần số cơ
bản:
| Dạng sóng | Họa âm chứa | Cảm giác nghe |
|---|---|---|
sine |
Chỉ đúng 1 tần số cơ bản, không họa âm | Tinh khiết, êm, giống tiếng sáo/còi |
square |
Chỉ họa âm bậc lẻ (1, 3, 5...), biên độ giảm theo 1/n | Rỗng, "kèn" điện tử, giống tiếng game 8-bit |
sawtooth |
Mọi họa âm (lẻ + chẵn), biên độ giảm theo 1/n | Sáng, gắt, giàu texture — nền tảng âm synth "brassy" |
triangle |
Chỉ họa âm bậc lẻ, biên độ giảm rất nhanh theo 1/n² | Mềm hơn square nhưng vẫn có màu sắc, giữa sine và square |
Càng nhiều họa âm bậc cao và biên độ chúng càng lớn, âm thanh càng "sáng"/nhiều texture — đây chính là
lý do sawtooth nghe gắt hơn hẳn sine dù phát cùng 1 tần số cơ bản.
2. Tần Số & detune: Tinh Chỉnh Cao Độ
oscillator.frequency đặt cao độ chính (Hz). oscillator.detune tinh chỉnh
thêm theo đơn vị cents (1 cent = 1/100 nửa cung, 100 cents = đúng 1 nửa cung). Tần số
phát ra thực tế được tính:
$$f_{\text{actual}} = f_{\text{base}} \times 2^{\,\text{detune}/1200}$$
3. ADSR Envelope: Điều Khiển Âm Lượng Theo Thời Gian
1 nốt nhạc thật không bật/tắt tức thì — nó có hình dạng âm lượng theo 4 giai đoạn, điều khiển bằng
chính GainNode đã học ở Bài 1:
| Giai đoạn | Ý nghĩa | Kích hoạt khi nào |
|---|---|---|
| Attack | Thời gian tăng từ 0 lên đỉnh | Ngay khi nhấn phím (note-on) |
| Decay | Thời gian giảm từ đỉnh xuống mức sustain | Ngay sau attack, vẫn khi giữ phím |
| Sustain | Mức âm lượng giữ nguyên | Trong lúc còn giữ phím |
| Release | Thời gian giảm về 0 sau khi buông phím | Ngay khi thả phím (note-off) |
exponentialRampToValueAtTime không thể nhắm tới giá trị 0
linearRampToValueAtTime(0, ...) hoặc setTargetAtTime (tiệm cận rất gần 0)
thay vì exponential ramp.
stop() sau khi Release kết thúc
gainNode.disconnect() chỉ ngắt khỏi đồ thị âm thanh (im lặng) — oscillator
vẫn tiếp tục chạy, vẫn tốn CPU, cho tới khi được gọi .stop() rõ ràng.
Luôn lên lịch oscillator.stop(audioContext.currentTime + releaseTime) ngay khi note-off
xảy ra — không cần setTimeout riêng, vì stop() nhận thẳng 1 thời điểm
trong tương lai theo audio-clock.
Sân chơi tương tác: Mini Synth Chơi Bằng Bàn Phím
Gõ các phím A W S E D F T G Y H U J K O L P ; trên bàn phím máy tính (hoặc bấm trực tiếp
vào phím đàn bên dưới) để chơi 1 quãng rưỡi từ C4 tới E5. Chỉnh dạng sóng, detune, và 4 slider ADSR —
quan sát hình dạng bao hình cập nhật theo thời gian thực khi giữ/buông phím.
Dạng sóng
ADSR Envelope
Nhật ký
Trắc nghiệm ôn tập
Câu 1: Vì sao sawtooth nghe "sáng/gắt" hơn hẳn sine dù phát cùng 1 tần số
cơ bản?
Trắc nghiệm ôn tập
Câu 2: Vì sao 2 oscillator phát cùng nốt nhưng detune khác nhau vài cents lại tạo cảm
giác âm thanh "dày" hơn 1 oscillator đơn?
Trắc nghiệm ôn tập
Câu 3: Vì sao phải luôn lên lịch oscillator.stop() sau khi giai đoạn Release kết thúc,
thay vì chỉ disconnect()?