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}$$

🔬 Đào sâu: vì sao detune nhẹ tạo hiệu ứng "dày" (unison/chorus)?
Khi 2 oscillator phát gần như cùng tần số nhưng lệch nhau vài cents, chúng không hoàn toàn đồng pha — tạo ra hiện tượng phách (beating) nhẹ, nghe rộng/dày hơn hẳn 1 nguồn đơn. Đây là kỹ thuật "unison detune" kinh điển trong mọi synth: nhân đôi oscillator, giữ nguyên tần số gốc, detune 1 bên +vài cents và bên kia -vài cents.

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
Ramp dạng mũ (exponential) chia tỉ lệ giá trị hiện tại, nên không bao giờ đạt đúng 0 (chia cho 0 vô nghĩa) — gọi với target 0 sẽ ném lỗi. Với giai đoạn Release (giảm về im lặng), hầu hết synth dùng linearRampToValueAtTime(0, ...) hoặc setTargetAtTime (tiệm cận rất gần 0) thay vì exponential ramp.
🕳️ Cạm bẫy thường gặp: quên 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.

🎹 Sân chơi tương tác: Mini Synth Bàn Phím

Dạng sóng

ADSR Envelope

Nhật ký

audio-oscillator-synthesis-live.js

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()?

📖 Tài liệu tham khảo / References

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

Bài 3: Gain, Filter & Hiệu Ứng Bài 1: AudioContext & Đồ Thị Âm Thanh Quay lại Lộ trình Series Web Audio API