Trước khi có phần tử <audio> HTML5, bạn cần có Flash hoặc một trình bổ trợ khác để phá vỡ sự im lặng của web. Mặc dù âm thanh trên web không còn
yêu cầu trình bổ trợ, thẻ âm thanh mang lại những hạn chế đáng kể cho
triển khai các trò chơi phức tạp và ứng dụng tương tác.
API Web Audio là một API JavaScript cấp cao để xử lý và tổng hợp âm thanh trong các ứng dụng web. Mục tiêu của API này là bao gồm các chức năng có trong công cụ âm thanh trò chơi hiện đại và một số tác vụ trộn, xử lý và lọc có trong các ứng dụng sản xuất âm thanh hiện đại trên máy tính. Sau đây là phần giới thiệu ngắn gọn về cách sử dụng API mạnh mẽ này.
Bắt đầu với AudioContext
AudioContext dùng để quản lý và phát tất cả âm thanh. Để tạo
âm thanh bằng API Âm thanh web, hãy tạo một hoặc nhiều nguồn âm thanh
rồi kết nối các nguồn đó với đích đến âm thanh do thực thể AudioContext
cung cấp. Kết nối này không cần phải trực tiếp và có thể đi qua bất kỳ số lượng AudioNodes trung gian nào đóng vai trò là mô-đun xử lý cho tín hiệu âm thanh. Định tuyến này được mô tả chi tiết hơn trong quy cách của Web Audio.
Một thực thể duy nhất của AudioContext có thể hỗ trợ nhiều đầu vào âm thanh và biểu đồ âm thanh phức tạp, vì vậy, chúng ta chỉ cần một trong số này cho mỗi ứng dụng âm thanh mà chúng ta tạo.
Đoạn mã sau đây tạo một AudioContext:
var context;
window.addEventListener('load', init, false);
function init() {
try {
context = new AudioContext();
}
catch(e) {
alert('Web Audio API is not supported in this browser');
}
}
Đối với các trình duyệt dựa trên WebKit cũ hơn, hãy sử dụng tiền tố webkit, như với webkitAudioContext.
Nhiều chức năng thú vị của API Âm thanh web như tạo AudioNodes và giải mã dữ liệu tệp âm thanh là các phương thức của AudioContext.
Đang tải âm thanh
Web Audio API sử dụng AudioBuffer cho các âm thanh có độ dài từ ngắn đến trung bình. Cách cơ bản là sử dụng XMLHttpRequest để tìm nạp tệp âm thanh.
API này hỗ trợ tải dữ liệu tệp âm thanh ở nhiều định dạng, chẳng hạn như WAV, MP3, AAC, OGG và các định dạng khác. Khả năng hỗ trợ của trình duyệt đối với các định dạng âm thanh khác nhau sẽ khác nhau.
Đoạn mã sau đây minh hoạ cách tải một mẫu âm thanh:
var dogBarkingBuffer = null;
var context = new AudioContext();
function loadDogSound(url) {
var request = new XMLHttpRequest();
request.open('GET', url, true);
request.responseType = 'arraybuffer';
// Decode asynchronously
request.onload = function() {
context.decodeAudioData(request.response, function(buffer) {
dogBarkingBuffer = buffer;
}, onError);
}
request.send();
}
Dữ liệu tệp âm thanh là tệp nhị phân (không phải văn bản), vì vậy, chúng ta đặt responseType của yêu cầu thành 'arraybuffer'. Để biết thêm thông tin về ArrayBuffers, hãy xem bài viết này về XHR2.
Sau khi nhận được dữ liệu tệp âm thanh (chưa giải mã), bạn có thể giữ lại dữ liệu đó để giải mã sau hoặc giải mã ngay bằng phương thức decodeAudioData() của AudioContext. Phương thức này lấy ArrayBuffer của dữ liệu tệp âm thanh được lưu trữ trong request.response và giải mã không đồng bộ (không chặn luồng thực thi JavaScript chính).
Khi decodeAudioData() hoàn tất, hàm này sẽ gọi một hàm callback cung cấp dữ liệu âm thanh PCM đã giải mã dưới dạng AudioBuffer.
Đang phát âm thanh
Sau khi tải một hoặc nhiều AudioBuffers, chúng ta có thể phát âm thanh. Giả sử chúng ta vừa tải một AudioBuffer có tiếng chó sủa và quá trình tải đã hoàn tất. Sau đó, chúng ta có thể phát vùng đệm này bằng đoạn mã sau.
var context = new AudioContext();
function playSound(buffer) {
var source = context.createBufferSource(); // creates a sound source
source.buffer = buffer; // tell the source which sound to play
source.connect(context.destination); // connect the source to the context's destination (the speakers)
source.noteOn(0); // play the source now
}
Hàm playSound() này có thể được gọi mỗi khi có người nhấn phím hoặc nhấp vào nội dung nào đó bằng chuột.
Hàm noteOn(time) giúp bạn dễ dàng lên lịch phát âm thanh chính xác cho trò chơi và các ứng dụng khác có yêu cầu khắt khe về thời gian. Tuy nhiên, để đảm bảo việc lập lịch biểu này hoạt động đúng cách, hãy đảm bảo rằng vùng đệm âm thanh của bạn đã được tải trước.
Tóm tắt Web Audio API
Tất nhiên, tốt hơn là bạn nên tạo một hệ thống tải chung hơn, không được cố định giá trị trong mã để tải âm thanh cụ thể này. Có nhiều cách để xử lý nhiều âm thanh có độ dài từ ngắn đến trung bình mà một ứng dụng âm thanh hoặc trò chơi sẽ sử dụng – dưới đây là một cách sử dụng BufferLoader (không thuộc tiêu chuẩn web).
Sau đây là ví dụ về cách bạn có thể sử dụng lớp BufferLoader.
Hãy tạo hai AudioBuffers; và ngay khi chúng được tải, hãy phát chúng cùng một lúc.
window.onload = init;
var context;
var bufferLoader;
function init() {
context = new AudioContext();
bufferLoader = new BufferLoader(
context,
[
'../sounds/hyper-reality/br-jam-loop.wav',
'../sounds/hyper-reality/laughter.wav',
],
finishedLoading
);
bufferLoader.load();
}
function finishedLoading(bufferList) {
// Create two sources and play them both together.
var source1 = context.createBufferSource();
var source2 = context.createBufferSource();
source1.buffer = bufferList[0];
source2.buffer = bufferList[1];
source1.connect(context.destination);
source2.connect(context.destination);
source1.noteOn(0);
source2.noteOn(0);
}
Xử lý thời gian: Phát âm thanh theo nhịp điệu
Web Audio API cho phép nhà phát triển lên lịch phát chính xác. Để minh hoạ điều này, hãy thiết lập một bản nhạc nhịp điệu đơn giản. Có lẽ mẫu bộ trống được biết đến rộng rãi nhất là mẫu sau:
trong đó hihat được chơi ở mỗi nốt móc đơn, còn trống kick và trống snare được chơi luân phiên ở mỗi nốt đen, theo nhịp 4/4.
Giả sử chúng ta đã tải các vùng đệm kick, snare và hihat, thì mã để thực hiện việc này rất đơn giản:
for (var bar = 0; bar < 2; bar++) {
var time = startTime + bar * 8 * eighthNoteTime;
// Play the bass (kick) drum on beats 1, 5
playSound(kick, time);
playSound(kick, time + 4 * eighthNoteTime);
// Play the snare drum on beats 3, 7
playSound(snare, time + 2 * eighthNoteTime);
playSound(snare, time + 6 * eighthNoteTime);
// Play the hi-hat every eighth note.
for (var i = 0; i < 8; ++i) {
playSound(hihat, time + i * eighthNoteTime);
}
}
Ở đây, chúng ta chỉ lặp lại một lần thay vì lặp lại không giới hạn như trong bản nhạc. Hàm playSound là một phương thức phát vùng đệm tại một thời điểm cụ thể, như sau:
function playSound(buffer, time) {
var source = context.createBufferSource();
source.buffer = buffer;
source.connect(context.destination);
source.noteOn(time);
}
Thay đổi âm lượng của âm thanh
Một trong những thao tác cơ bản nhất mà bạn có thể muốn thực hiện đối với âm thanh là thay đổi âm lượng. Bằng cách sử dụng API Âm thanh web, chúng ta có thể định tuyến nguồn đến đích thông qua AudioGainNode để điều chỉnh âm lượng:

Bạn có thể thiết lập kết nối này như sau:
// Create a gain node.
var gainNode = context.createGainNode();
// Connect the source to the gain node.
source.connect(gainNode);
// Connect the gain node to the destination.
gainNode.connect(context.destination);
Sau khi thiết lập biểu đồ, bạn có thể thay đổi âm lượng theo phương thức lập trình bằng cách thao tác với gainNode.gain.value như sau:
// Reduce the volume.
gainNode.gain.value = 0.5;
Chuyển dần giữa hai âm thanh
Bây giờ, giả sử chúng ta có một tình huống phức tạp hơn một chút, trong đó chúng ta đang phát nhiều âm thanh nhưng muốn chuyển dần giữa các âm thanh đó. Đây là một trường hợp phổ biến trong ứng dụng giống như DJ, trong đó chúng ta có hai bàn xoay và muốn có thể xoay từ nguồn âm thanh này sang nguồn âm thanh khác.
Bạn có thể thực hiện việc này bằng biểu đồ âm thanh sau:
Để thiết lập, chúng ta chỉ cần tạo hai AudioGainNodes và kết nối từng nguồn thông qua các nút, sử dụng một hàm như sau:
function createSource(buffer) {
var source = context.createBufferSource();
// Create a gain node.
var gainNode = context.createGainNode();
source.buffer = buffer;
// Turn on looping.
source.loop = true;
// Connect source to gain.
source.connect(gainNode);
// Connect gain to destination.
gainNode.connect(context.destination);
return {
source: source,
gainNode: gainNode
};
}
Làm mờ chéo công suất bằng nhau
Phương pháp làm mờ chéo tuyến tính đơn giản cho thấy âm lượng giảm khi bạn xoay giữa các mẫu.
Để giải quyết vấn đề này, chúng tôi sử dụng đường cong công suất bằng nhau, trong đó các đường cong khuếch đại tương ứng là phi tuyến tính và giao nhau ở biên độ cao hơn. Điều này giúp giảm thiểu tình trạng sụt giảm âm lượng giữa các vùng âm thanh, nhờ đó tạo ra hiệu ứng chuyển tiếp mượt mà hơn giữa các vùng có thể có mức âm lượng hơi khác nhau.
Chuyển tiếp danh sách phát
Một ứng dụng phổ biến khác của thanh trượt chuyển tiếp là dành cho ứng dụng trình phát nhạc.
Khi một bài hát thay đổi, chúng ta muốn làm mờ dần bản nhạc hiện tại và làm mờ dần bản nhạc mới để tránh chuyển đổi đột ngột. Để làm việc này, hãy lên lịch
hiệu ứng chuyển mờ dần trong tương lai. Mặc dù chúng ta có thể sử dụng setTimeout để thực hiện việc lập lịch biểu này, nhưng cách này không chính xác. Với API Âm thanh web, chúng ta có thể sử dụng giao diện AudioParam để lên lịch cho các giá trị trong tương lai của các tham số, chẳng hạn như giá trị khuếch đại của AudioGainNode.
Do đó, với một danh sách phát nhất định, chúng ta có thể chuyển đổi giữa các bản nhạc bằng cách lên lịch giảm mức tăng trên bản nhạc đang phát và tăng mức tăng trên bản nhạc tiếp theo, cả hai đều diễn ra ngay trước khi bản nhạc hiện tại phát xong:
function playHelper(bufferNow, bufferLater) {
var playNow = createSource(bufferNow);
var source = playNow.source;
var gainNode = playNow.gainNode;
var duration = bufferNow.duration;
var currTime = context.currentTime;
// Fade the playNow track in.
gainNode.gain.linearRampToValueAtTime(0, currTime);
gainNode.gain.linearRampToValueAtTime(1, currTime + ctx.FADE_TIME);
// Play the playNow track.
source.noteOn(0);
// At the end of the track, fade it out.
gainNode.gain.linearRampToValueAtTime(1, currTime + duration-ctx.FADE_TIME);
gainNode.gain.linearRampToValueAtTime(0, currTime + duration);
// Schedule a recursive track change with the tracks swapped.
var recurse = arguments.callee;
ctx.timer = setTimeout(function() {
recurse(bufferLater, bufferNow);
}, (duration - ctx.FADE_TIME) - 1000);
}
API Âm thanh web cung cấp một tập hợp các phương thức RampToValue thuận tiện để thay đổi dần giá trị của một tham số, chẳng hạn như linearRampToValueAtTime và exponentialRampToValueAtTime.
Mặc dù có thể chọn hàm thời gian chuyển đổi từ các hàm tuyến tính và hàm mũ tích hợp sẵn (như trên), nhưng bạn cũng có thể chỉ định đường cong giá trị của riêng mình thông qua một mảng giá trị bằng hàm setValueCurveAtTime.
Áp dụng hiệu ứng bộ lọc đơn giản cho âm thanh

API Âm thanh web cho phép bạn truyền âm thanh từ nút âm thanh này sang nút âm thanh khác, tạo ra một chuỗi bộ xử lý có thể phức tạp để thêm các hiệu ứng phức tạp vào dạng âm thanh của bạn.
Một cách để thực hiện việc này là đặt BiquadFilterNode giữa nguồn âm thanh và đích đến. Loại nút âm thanh này có thể thực hiện nhiều bộ lọc bậc thấp, có thể dùng để tạo bộ chỉnh âm đồ hoạ và thậm chí là các hiệu ứng phức tạp hơn, chủ yếu liên quan đến việc chọn phần nào trong phổ tần số của âm thanh cần nhấn mạnh và phần nào cần giảm bớt.
Các loại bộ lọc được hỗ trợ bao gồm:
- Bộ lọc thông thấp
- Bộ lọc thông cao
- Bộ lọc dải thông
- Bộ lọc kệ thấp
- Bộ lọc kệ cao
- Bộ lọc tăng âm
- Bộ lọc khía
- Bộ lọc thông dải
Tất cả bộ lọc đều có các tham số để chỉ định một lượng mức tăng, tần suất áp dụng bộ lọc và hệ số chất lượng. Bộ lọc thông thấp giữ lại dải tần số thấp hơn nhưng loại bỏ tần số cao. Điểm ngắt được xác định theo giá trị tần số, còn hệ số Q không có đơn vị và xác định hình dạng của biểu đồ. Mức tăng chỉ ảnh hưởng đến một số bộ lọc nhất định, chẳng hạn như bộ lọc kệ thấp và bộ lọc đỉnh, chứ không ảnh hưởng đến bộ lọc thông thấp này.
Hãy thiết lập một bộ lọc thông thấp đơn giản để chỉ trích xuất các âm trầm từ một mẫu âm thanh:
// Create the filter
var filter = context.createBiquadFilter();
// Create the audio graph.
source.connect(filter);
filter.connect(context.destination);
// Create and specify parameters for the low-pass filter.
filter.type = 0; // Low-pass filter. See BiquadFilterNode docs
filter.frequency.value = 440; // Set cutoff to 440 HZ
// Playback the sound.
source.noteOn(0);
Nhìn chung, các chế độ điều khiển tần số cần được điều chỉnh để hoạt động theo thang đo logarit vì thính giác của con người cũng hoạt động theo cùng một nguyên tắc (tức là A4 là 440 Hz và A5 là 880 Hz). Để biết thêm thông tin, hãy xem hàm FilterSample.changeFrequency trong đường liên kết đến mã nguồn ở trên.
Cuối cùng, xin lưu ý rằng mã mẫu cho phép bạn kết nối và ngắt kết nối bộ lọc, thay đổi linh động biểu đồ AudioContext. Chúng ta có thể ngắt kết nối
AudioNodes khỏi biểu đồ bằng cách gọi node.disconnect(outputNumber).
Ví dụ: để định tuyến lại biểu đồ từ việc đi qua một bộ lọc đến một kết nối trực tiếp, chúng ta có thể làm như sau:
// Disconnect the source and filter.
source.disconnect(0);
filter.disconnect(0);
// Connect the source directly.
source.connect(context.destination);
Nghe thêm
Chúng ta đã tìm hiểu những kiến thức cơ bản về API, bao gồm cả việc tải và phát các mẫu âm thanh. Chúng tôi đã tạo biểu đồ âm thanh bằng các nút và bộ lọc khuếch đại, đồng thời lên lịch cho âm thanh và điều chỉnh thông số âm thanh để bật một số hiệu ứng âm thanh phổ biến. Đến đây, bạn đã sẵn sàng để bắt đầu xây dựng một số ứng dụng âm thanh web thú vị!
Nếu bạn đang tìm kiếm nguồn cảm hứng, nhiều nhà phát triển đã tạo ra những tác phẩm tuyệt vời bằng API Web Audio. Một số tính năng tôi yêu thích nhất bao gồm:
- AudioJedit, một công cụ ghép âm thanh trong trình duyệt sử dụng các đường liên kết cố định của SoundCloud.
- ToneCraft, một bộ sắp xếp âm thanh, trong đó âm thanh được tạo bằng cách xếp chồng các khối 3D.
- Plink, một trò chơi tạo nhạc cộng tác sử dụng Web Audio và Web Sockets.