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

Trước phần tử <audio> HTML5, Flash hoặc một trình bổ trợ khác là cần thiết để 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 một trình bổ trợ, nhưng thẻ âm thanh mang đến 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 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 chung 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 phiên bản 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à các 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 Web Audio.

Một phiên bản duy nhất của AudioContext có thể hỗ trợ nhiều đầu vào âm thanh và đồ thị âm thanh phức tạp, vì vậy, chúng ta sẽ chỉ cần một phiên bản 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 dùng tiền tố webkit, chẳng hạn như webkitAudioContext.

Nhiều chức năng thú vị của Web Audio API, 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 â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 các 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ẽ thay đổi.

Đ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à dữ liệu 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 được 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() AudioContext. Phương thức này lấy ArrayBuffer 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.

Phát âm thanh

Sau khi tải một hoặc nhiều AudioBuffers, chúng ta sẽ sẵn sàng phát âm thanh. Giả sử chúng ta vừa tải một AudioBuffer có âm thanh 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ã 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 người dùng nhấn một phím hoặc nhấp vào một 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 các trò chơi và ứng dụng quan trọng khác về thời gian. Tuy nhiên, để tính năng lập lịch này hoạt động đúng cách, hãy đảm bảo rằng các 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, sẽ tốt hơn nếu bạ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à một ứng dụng hoặc trò chơi âm thanh sẽ sử dụng – đâ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 2 AudioBuffers; và ngay khi chúng được tải, hãy phát chúng 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

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:

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

trong đó hihat được chơi ở mỗi nốt thứ tám, còn kick và snare được chơi luân phiên ở mỗi nốt phần tư, trong nhịp 4/4.

Giả sử chúng ta đã tải các 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ỉ 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 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. Khi sử dụng Web Audio API, chúng ta có thể định tuyến nguồn đến đích thông qua một AudioGainNode để điều chỉnh âm lượng:

Sơ đồ quy trình đơn giản mô tả âm thanh truyền từ Nguồn đến GainNode rồi đến Đích.

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 đổi mượt giữa hai âm thanh

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ượt 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ể chuyển 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:

Đồ thị âm thanh có 2 nguồn được kết nối với các nút khuếch đại riêng biệt, sau đó được định tuyến vào cùng một đích đến.

Để thiết lập việc này, chúng ta chỉ cần tạo 2 AudioGainNodes và kết nối từng nguồn thông qua các nút, bằng cách 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
    };
}

Chuyển cảnh mờ dần với công suất bằng nhau

Phương pháp làm mờ tuyến tính đơn giản cho thấy âm lượng giảm khi bạn chuyển đổi giữa các mẫu.

Hiệu ứng chuyển cảnh tuyến tính được mô tả dưới dạng hai đường thẳng bổ sung được ánh xạ dưới dạng các mức biên độ theo thời gian.
Hiệu ứng mờ dầ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 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 sự sụt giảm âm lượng giữa các vùng âm thanh, dẫn đến hiệu ứng làm mờ đều hơn giữa các vùng có thể có mức độ khác nhau một chút.

Hiệu ứng chuyển cảnh mờ dần với công suất bằng nhau được mô tả dưới dạng hai đường cong được ánh xạ dưới dạng mức biên độ theo thời gian.
Hiệu ứng chuyển cảnh có công suất bằng nhau

Chế độ chuyển tiếp giữa các bài hát trong danh sách phát

Một ứng dụng phổ biến khác của bộ chỉnh âm lượng là 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ài hát hiện tại và làm mờ bài hát mới để tránh chuyển đổi đột ngột. Để làm việc này, hãy lên lịch cho một hiệu ứng chuyển cảnh trong tương lai. Mặc dù chúng ta có thể dùng setTimeout để lên lịch, nhưng cách này không chính xác. Với Web Audio API, chúng ta có thể 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ị khuếch đại của một AudioGainNode.

Do đó, khi có 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 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ù bạn có thể chọn hàm thời gian chuyển đổi từ các hàm tuyến tính và hàm số 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

Lưu đồ đơn giản mô tả âm thanh truyền từ Nguồn đến BiquadFilterNode rồi đến Đích.

Web Audio API cho phép bạn truyền âm thanh từ một nút âm thanh sang một nút 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 các dạng âm thanh.

Một cách để thực hiện việc này là đặt BiquadFilterNode giữa nguồn và đích đến của âm thanh. Loại nút âm thanh này có thể thực hiện nhiều bộ lọc bậc thấp. Bạn có thể dùng các bộ lọc này để tạo bộ cân bằng đồ hoạ và thậm chí cả các hiệu ứng phức tạp hơn, chủ yếu là để chọn những phần nào trong phổ tần số của âm thanh cần nhấn mạnh và những 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 thông dải
  • Bộ lọc giá thấp
  • Bộ lọc kệ trên
  • Bộ lọc đỉnh
  • Bộ lọc loại bỏ
  • Bộ lọc tất cả thẻ và vé

Tất cả các bộ lọc đều có các thông 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 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 đồ. 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, bạn cần điều chỉnh các chế độ kiểm soát tần số để hoạt động trên thang logarit vì thính giác của con người hoạt động theo nguyên tắc tương tự (tức là A4 là 440 Hz và A5 là 880 Hz). Để biết thêm 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, 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 biểu đồ AudioContext một cách linh hoạt. Chúng ta có thể ngắt kết nối AudioNode 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 này, bao gồm cả việc tải và phát các mẫu âm thanh. Chúng tôi đã tạo các biểu đồ âm thanh có nút và bộ lọc khuếch đại, đồng thời lên lịch điều chỉnh các thông số âm thanh và â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, thì nhiều nhà phát triển đã tạo ra những tác phẩm tuyệt vời bằng Web Audio API. Một số ứng dụng tôi yêu thích bao gồm:

  • AudioJedit là một công cụ cắt ghép âm thanh trong trình duyệt, sử dụng đường liên kết cố định của SoundCloud.
  • ToneCraft là một trình tạo chuỗi âm thanh, nơi âm thanh được tạo bằng cách xếp các khối 3D.
  • Plink là một trò chơi sáng tác nhạc cộng tác sử dụng Web Audio và WebSockets.