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ự tĩnh 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ợ, nhưng thẻ âm thanh lại mang lại những hạn chế đáng kể khi triển khai các trò chơi phức tạp và ứng dụng tương tác.
Web Audio API 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 tính năng có trong các 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 dành cho máy tính. Phần 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 sử dụng AudioContext
AudioContext dùng để quản lý và phát tất cả âm thanh. Để tạo âm thanh bằng Web Audio API, hãy tạo một hoặc nhiều nguồn âm thanh và 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. Quy trình định tuyến này được mô tả chi tiết hơn trong quy cách của Âm thanh trên web.
Một thực thể 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 sẽ 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 cũ dựa trên WebKit, 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 trên web, chẳng hạn 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
.
Tải âm thanh
API Web âm thanh sử dụng AudioBuffer cho âm thanh có độ dài từ ngắn đến trung bình. Phương pháp 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ợ trình duyệt cho các định dạng âm thanh 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ã dữ liệu đó một cách không đồng bộ (không chặn luồng thực thi JavaScript chính).
Khi hoàn tất, decodeAudioData()
sẽ gọi một hàm gọi lại cung cấp dữ liệu âm thanh PCM đã giải mã dưới dạng AudioBuffer
.
Phát âm thanh
Sau khi tải một hoặc nhiều AudioBuffers
, chúng ta đã sẵn sàng phát âm thanh. Giả sử chúng ta vừa tải một AudioBuffer
có âm thanh của một con 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 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 một mục 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 các trò chơi và các ứng dụng quan trọng về thời gian khác. Tuy nhiên, để tính năng lên lịch 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 API Âm thanh trên web
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 mã hoá cứng để tải âm thanh cụ thể này. Có nhiều phương pháp để xử lý nhiều âm thanh có độ dài từ ngắn đến trung bình mà ứng dụng âm thanh hoặc trò chơi sẽ sử dụng. Sau đâ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 tải xong, hãy phát đồng thời hai AudioBuffers
đó.
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
API Âm thanh trên web 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 phát mỗi nốt thứ tám, còn kick và snare được phát luân phiên mỗi nốt thứ tư, theo nhịp 4/4.
Giả sử chúng ta đã tải các bộ đệ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 vô hạn như trong bản nhạc. Hàm playSound
là một phương thức phát vùng đệm vào 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. Khi sử dụng API Web âm thanh, chúng ta có thể định tuyến nguồn đến đích thông qua AudioGainNode để thao tác â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;
Hiệu ứng chuyển tiếp 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 đổi giữa các âm thanh đó. Đây là trường hợp phổ biến trong một ứ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 điều này, 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 này, 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
};
}
Chèn âm thanh bằng công suất bằng nhau
Phương pháp chuyển đổi lồng ghép 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 ta sử dụng một đường cong công suất bằng nhau, trong đó các đường cong lợi tức tương ứng không tuyến tính và giao nhau ở biên độ cao hơn. Điều này giúp giảm thiểu sự sụt giảm âm lượng giữa các vùng âm thanh, dẫn đến hiệu ứng chuyển đổi mượt mà hơn giữa các vùng có thể có mức âm lượng khác nhau một chút.
Chuyển đổi liền mạch giữa các bản nhạc trong danh sách phát
Một ứng dụng phổ biến khác của bộ khuếch đại âm thanh là ứng dụng phát nhạc.
Khi một bài hát thay đổi, chúng ta muốn làm mờ bản nhạc hiện tại và làm mờ bản nhạc mới để tránh chuyển đổi chói tai. Để thực hiện việc này, hãy lên lịch hiệu ứng chuyển tiếp trong tương lai. Mặc dù chúng ta có thể sử dụng setTimeout
để lên lịch này, nhưng cách này không chính xác. Với API Âm thanh trên web, chúng ta có thể sử dụng giao diện AudioParam để lên lịch các giá trị trong tương lai cho các tham số, chẳng hạn như giá trị tăng của AudioGainNode
.
Do đó, với một danh sách phát, 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 độ lợi trên bản nhạc đang phát và tăng độ lợi 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 trên web cung cấp một bộ 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 cách sử dụng hàm setValueCurveAtTime
.
Áp dụng hiệu ứng bộ lọc đơn giản cho âm thanh
Web Audio API cho phép bạn chuyển âm thanh từ một nút âm thanh sang một 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 hình dạng âm thanh.
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ộ cân bằng đồ 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 của phổ tần số của âm thanh cần làm nổi bật và phần nào cần làm dịu.
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 thông băng tần
- Bộ lọc tần số thấp
- Bộ lọc kệ cao
- Bộ lọc làm nổi bật điểm đỉnh
- Bộ lọc rãnh
- Bộ lọc tất cả thẻ và vé
Và tất cả các bộ lọc đều bao gồm các thông số để chỉ định một số 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ỏ các tần số cao. Điểm ngắt được xác định bằng giá trị tần số và hệ số Q không có đơn vị và xác định hình dạng của biểu đồ. Độ lợi chỉ ảnh hưởng đến một số bộ lọc nhất định, chẳng hạn như bộ lọc tần số 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 cơ bản 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, bạn cần điều chỉnh các chế độ điều khiển tần số để hoạt động theo thang logarit vì chính thính giác của con người cũng hoạt động theo nguyên tắc tương tự (tức là A4 là 440hz và A5 là 880hz). Để biết thêm thông tin chi tiết, hãy xem hàm FilterSample.changeFrequency
trong đường liên kết đến mã nguồn ở trên.
Cuối cùng, hãy 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 sang 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 tăng và bộ lọc, âm thanh được lên lịch và các điều chỉnh thông số âm thanh để bật một số hiệu ứng âm thanh phổ biến. Lúc này, bạn đã sẵn sàng để xây dựng một số ứng dụng âm thanh web tuyệt vời!
Nếu bạn đang tìm nguồn cảm hứng, hãy tham khảo các sản phẩm tuyệt vời mà nhiều nhà phát triển đã tạo ra bằng Web Audio API. Một số tính năng tôi yêu thích bao gồm:
- AudioJedit, một công cụ ghép nối âm thanh trong trình duyệt sử dụng đường liên kết cố định trên SoundCloud.
- ToneCraft, một trình tạo trình tự âm thanh tạo âm thanh bằng cách xếp chồng các khối 3D.
- Plink, một trò chơi cộng tác sáng tác nhạc bằng Web Audio và Web Sockets.