Bắt đầu với API Web âm thanh

Trước khi có phần tử <audio> HTML5, cần có Flash hoặc một trình bổ trợ khác để phá vỡ sự im lặng của trang 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ông cụ âm thanh trò chơi hiện đại và một số tác vụ kết hợp, 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. Sau đây là phần giới thiệu sơ bộ 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 là trực tiếp và có thể đi qua số lượng AudioNodes trung gian đóng vai trò là mô-đun xử lý cho tín hiệu âm thanh. Quy trình này được mô tả chi tiết hơn trong thông số kỹ thuật của Âm thanh 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 dựa trên WebKit cũ, hãy sử dụng tiền tố webkit giống như với webkitAudioContext.

Nhiều chức năng thú vị của API Web âm thanh, chẳng hạn như tạo Nút Audio 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 callback cung cấp dữ liệu âm thanh PCM đã giải mã dưới dạng AudioBuffer.

Phát âm thanh

Biểu đồ âm thanh đơn giản
Biểu đồ âm thanh đơn giản

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, bạn nên tạo một hệ thống tải tổng quát 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

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ó thể mẫu trống của bộ trống phổ biến nhất như sau:

Một mẫu trống rock đơn giản
Một mẫu trống rock đơn giản

trong đó một hihat được chơi ở mỗi 1/8 nốt, còn cú đá và chọi còi được chơi luân phiên mỗi quý, trong 4/4 thời gian.

Giả sử chúng ta đã tải các bộ đệm kick, snarehihat, 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 mộ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:

Biểu đồ âm thanh có nút tăng
Biểu đồ âm thanh có nút tă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;

Mờ 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 làm mờ và 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, khi chúng ta có 2 mâm đĩa than và muốn có thể lia 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:

Biểu đồ âm thanh có hai nguồn được kết nối thông qua các nút tăng
Biểu đồ âm thanh có hai nguồn được kết nối thông qua các nút tăng

Để 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ạy song song 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.

Hiệu ứng chuyển đổi tuyến tính
Phân tích tuyến tính

Để 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.

Một hiệu ứng chuyển đổi âm thanh có công suất bằng nhau.
Hiệu ứng chuyển đổi âm thanh có công suất bằng nhau

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 kết hợp phổ biến khác 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ờ 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);
}

Web Audio API cung cấp một nhóm 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ư linearRampToValueAtTimeexponentialRampToValueAtTime.

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

Biểu đồ âm thanh có BiquadFilterNode
Biểu đồ âm thanh có BiquadFilterNode

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 báo 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ữ 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, qua đó tự động thay đổi biểu đồ AudioContext. Chúng ta có thể ngắt kết nối các Nút Audio 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 theo 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, nhiều nhà phát triển đã tạo tác phẩm hay bằng API Web âm thanh. 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.