Mọi thứ trong Web Audio API bắt đầu từ 1 đối tượng duy nhất: AudioContext. Bài này xây
nền tảng bắt buộc trước khi chạm vào synth, filter, hay spectrum: hiểu đúng mô hình
đồ thị node, vòng đời của context, và vì sao trình duyệt không cho bạn phát âm thanh
ngay khi trang vừa tải xong.
1. AudioContext Là Gì?
AudioContext là "nhạc trưởng" quản lý toàn bộ hệ thống âm thanh của trang: nó giữ
tần số lấy mẫu (sample rate — thường 44100 hoặc 48000 Hz, cố định suốt vòng đời
context), 1 đồng hồ thời gian riêng (currentTime, tính bằng giây, chạy độc lập với
Date.now()), và là nơi mọi AudioNode được tạo ra từ đó
(audioContext.createOscillator(), .createGain()...).
2. Đồ Thị Node: Nguồn → Xử Lý → Đích
Âm thanh không "phát" bằng 1 lệnh đơn — nó chảy qua 1 chuỗi node được nối bằng connect(),
giống hệt dây cắm trên bàn mixer:
| Vai trò | Ví dụ Node | Nhiệm vụ |
|---|---|---|
| Nguồn (source) | OscillatorNode, AudioBufferSourceNode |
Tạo ra tín hiệu âm thanh ban đầu. |
| Xử lý (processing) | GainNode, BiquadFilterNode |
Biến đổi tín hiệu đi qua (âm lượng, lọc tần số...). |
| Đích (destination) | audioContext.destination |
Điểm cuối — thường là loa của thiết bị. |
osc.connect(gain).connect(audioContext.destination) nối 3 node này thành 1 đường ống —
đây chính xác là "đồ thị" (graph) trong tên gọi Web Audio API.
start() được ĐÚNG 1 LẦN
OscillatorNode và AudioBufferSourceNode giống 1 que diêm — dùng 1 lần rồi
bỏ. Gọi .start() lần thứ 2 trên cùng 1 node sẽ ném lỗi InvalidStateError.
Muốn phát lại, bạn phải tạo 1 node hoàn toàn mới — đây là lý do mọi ví dụ synth
thực tế đều tạo oscillator mới cho mỗi nốt nhạc, dù tái sử dụng cùng 1 GainNode/AudioContext
phía sau nó.
3. Vòng Đời Context: suspended → running → closed
AudioContext luôn khởi tạo ở trạng thái suspended (tạm dừng) —
không tự động running. Gọi audioContext.resume() để bắt đầu
xử lý audio thật sự. close() giải phóng tài nguyên vĩnh viễn (không thể resume lại
context đã đóng, phải tạo context mới).
AudioContext bên trong 1 sự kiện người dùng
new AudioContext() tới đúng lúc người dùng bấm nút "Phát" lần đầu — thay
vì tạo sẵn khi trang load rồi phải gọi resume() riêng.
4. Autoplay Policy: Vì Sao Cần Cử Chỉ Người Dùng?
Trình duyệt hiện đại chặn mọi trang tự động phát âm thanh khi vừa tải xong — lý do đơn giản: quá nhiều
website/quảng cáo từng lạm dụng điều này gây phiền. AudioContext chỉ chuyển sang
running khi được tạo/resume bên trong 1 tương tác thực của người dùng
(click, tap, phím) — gọi resume() từ 1 setTimeout hay khi trang vừa load sẽ
không có tác dụng.
AudioParam lên lịch theo audio-clock, không phải JS event loop
gainNode.gain hay oscillator.frequency là
AudioParam — có thể gán tức thời (.value = 440), nhưng cũng có thể
lên lịch chính xác bằng
setValueAtTime(440, audioContext.currentTime + 0.5). Vì audio xử lý trên luồng riêng ở
tần số lấy mẫu cố định, lịch này chính xác tới từng mẫu — hoàn toàn không bị ảnh hưởng bởi độ
trễ/jitter của vòng lặp sự kiện JS chính, khác hẳn setTimeout vốn chỉ đảm bảo "tối
thiểu bấy nhiêu mili-giây" chứ không chính xác tuyệt đối.
Sân chơi tương tác: Phát 1 Tone Bằng OscillatorNode
Bấm "Phát" để tạo AudioContext (đúng lúc user gesture), nối
OscillatorNode → GainNode → destination, và nghe âm thanh thật. Trong lúc đang phát, kéo
tần số/âm lượng để thấy AudioParam cập nhật tức thì trên cùng 1 node — không cần tạo node
mới. Bấm "Dừng" rồi "Phát" lại để thấy log giải thích vì sao phải tạo oscillator mới.
Điều khiển
Nhật ký
Trắc nghiệm ôn tập
Câu 1: Vì sao gọi oscillator.start() lần thứ 2 trên cùng 1
OscillatorNode sẽ ném lỗi?
Trắc nghiệm ôn tập
Câu 2: Vì sao không thể tạo AudioContext và phát nhạc ngay khi trang vừa tải xong?
Trắc nghiệm ôn tập
Câu 3: Điều gì khiến lên lịch bằng
AudioParam.setValueAtTime(value, audioContext.currentTime + delay) chính xác hơn
setTimeout?