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

Trướ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 web. Mặc dù âm thanh trên web không còn cần đến trình bổ trợ, nhưng thẻ âm thanh mang lại những hạn chế đáng kể trong việc triển khai các ứng dụng tương tác và trò chơi phức tạp.

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 cũng như một số tác vụ phối, xử lý và lọc có trong các ứng dụng tạo â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 API Web Audio, 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 âm thanh do thực thể AudioContext cung cấp. Kết nối này không nhất thiết phải trực tiếp và có thể đi qua số lượng AudioNodes trung gian đóng vai trò là mô-đun xử lý tín hiệu âm thanh. Định tuyến này được mô tả chi tiết hơn trong quy cách Â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 đã tạo.

Đoạn mã sau đây sẽ 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, như với webkitAudioContext.

Nhiều chức năng thú vị của API Web Audio, chẳng hạn như tạo AudioNode 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. 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ư WV, 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 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 tôi đặ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 mã hoá), bạn có thể giữ lại dữ liệu đó để giải mã sau hoặc có thể giải mã ngay bằng phương thức decodeAudioData() AudioContext. Phương thức này lấy ArrayBuffer của dữ liệu tệp âm thanh 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 hoàn tất, decodeAudioData() sẽ gọi một hàm callback. Hàm này cung cấp dữ liệu âm thanh PCM đã giải mã dưới dạng AudioBuffer.

Đang phát âm thanh

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

Sau khi một hoặc nhiều AudioBuffers được tải, chúng ta đã sẵn sàng phát âm thanh. Giả sử chúng ta vừa tải một AudioBuffer kèm theo 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 một 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 chuột vào mục nào đó.

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 quan trọng về thời gian khác. Tuy nhiên, để tính năng lên lịch biểu này hoạt động đúng cách, hãy đảm bảo rằng bạn đã tải sẵn vùng đệm âm thanh.

Tóm tắt API Web âm thanh

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à một ứng dụng âm thanh hoặc trò chơi sẽ sử dụng – sau đây là một cách 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 2 AudioBuffers; và ngay sau khi tải xong, hãy phát lại cùng 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

API Web âm thanh cho phép nhà phát triển lên lịch phát lại một cách 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. Sau đây là mẫu bộ trống phổ biến nhất:

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

trong đó một hihat được phát mỗi nốt nhạc thứ 8, còn cú đá và snare được chơi xen kẽ mỗi quý, trong 4/4 lần.

Giả sử chúng ta đã tải vùng đệm kick, snarehihat, 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ỉ tạo một lần lặp lại, 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 mộ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 Web âm thanh, chúng ta có thể định tuyến nguồn đến đích đến đó thông qua một AudioGainNode để điều khiển âm lượng:

Biểu đồ âm thanh có nút khuếch đại
Biểu đồ âm thanh có nút thu được

Bạn có thể thiết lập mối kết nối 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 biểu đồ được thiết lập, 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 chuyển đổi độ mờ 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, nơi chúng tôi có hai mâm đĩa than 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:

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

Để thiết lập chế độ 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 bằng cách dùng hàm tương tự 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
    };
}

Chuyển đổi công suất bằng nhau

Phương pháp chuyển đổi tuyến tính đơn thuần thể hiện sự sụt giảm âm lượng khi bạn xoay giữa các mẫu.

Mờ dần tuyến tính
Giao diện tuyến tính

Để 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 đạt được 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 sự sụt giảm âm lượng giữa các vùng âm thanh, dẫn đến sự giao thoa đều hơn giữa các khu vực có thể hơi khác nhau về cấp độ.

Chuyển đổi năng lượng bằng nhau.
Sự chuyển đổi công suất bằng nhau

Chuyển đổi danh sách phát

Một ứng dụng chuyển đổi phổ biến khác 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ờ bản nhạc hiện tại và làm mờ dần âm thanh mới để tránh quá trình chuyển đổi gây khó chịu. Để làm việc này, hãy lên lịch chuyển đổi trong tương lai. Mặc dù chúng ta có thể sử dụng setTimeout để lập lịch biểu này, nhưng điều này không chính xác. Với API Web âm thanh, 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 cho các tham số, chẳng hạn như giá trị thu được 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 mức tăng trên bản nhạc đang phát và tăng mức tăng ở bản tiếp theo, cả hai đều 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 Web âm thanh cung cấp một tập hợp 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ù bạn có thể chọn hàm thời gian chuyển đổi từ các hàm tuyến tính và luỹ thừa 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 lọc đơn giản cho âm thanh

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

API Web âm thanh cho phép bạn chuyể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ý phức tạp để thêm các hiệu ứng phức tạp vào 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ể dùng nhiều bộ lọc bậc thấp để tạo bộ cân bằng đồ hoạ và thậm chí là nhiều hiệu ứng phức tạp hơn, chủ yếu là để 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 để giảm dần.

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 qua băng tần
  • Bộ lọc kệ thấp
  • Bộ lọc kệ cao
  • Bộ lọc đạt đỉnh
  • Bộ lọc vết khía
  • Bộ lọc tất cả thẻ và vé

Đồng thời, tất cả bộ lọc đều bao gồm các tham 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ỏ tần suất cao. Điểm ngắt được xác định theo giá trị tần suất, còn hệ số Q là không đơn vị, đồng thời 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ư các bộ lọc mức thấp và cao nhất, 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 thanh 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, các chế độ điều khiển tần số cần được điều chỉnh để hoạt động ở thang đo logarit vì thính giác của con người 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 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ừ đi qua một bộ lọc sang 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 đã đề cập đến các khái niệm cơ bản của API này, bao gồm cả việc tải và phát mẫu âm thanh. Chúng tôi đã xây dựng các biểu đồ âm thanh với các nút và bộ lọc độ lợi, cũng như các tinh chỉnh âm thanh theo lịch biểu và tham số âm thanh để cho phép một số hiệu ứng âm thanh phổ biến. Đến đây, bạn đã sẵn sàng để xây dựng một số ứng dụng âm thanh hấp dẫn trên web!

Nếu bạn đang tìm nguồn cảm hứng, nhiều nhà phát triển đã tạo công việc tuyệt vời bằng API Web Audio. Sau đây là một số tính năng mà tôi yêu thích:

  • AudioJedit, một công cụ ghép âm thanh trong trình duyệt sử dụng đường liên kết cố định của SoundCloud.
  • ToneCraft, một trình tạo tuần tự âm thanh, trong đó âm thanh được tạo ra bằng cách xếp chồng các khối 3D.
  • Plink, một trò chơi cộng tác tạo nhạc bằng Âm thanh web và Cổng web.