Nghiên cứu điển hình – ỨNG TÁC với Chrome

Cách chúng tôi tạo ra âm thanh tuyệt vời

Oskar Eriksson
Oskar Eriksson

Giới thiệu

JAM with Chrome là một dự án âm nhạc trên web do Google tạo ra. JAM with Chrome cho phép mọi người trên khắp thế giới thành lập ban nhạc và chơi nhạc cùng nhau theo thời gian thực bên trong trình duyệt. Chúng tôi tại DinahMoe rất vui khi được tham gia dự án này. Vai trò của chúng tôi là sản xuất nhạc cho ứng dụng, đồng thời thiết kế và phát triển thành phần âm nhạc. Quá trình phát triển bao gồm 3 phần chính: "music workstation" (máy trạm âm nhạc) bao gồm tính năng phát midi, bộ lấy mẫu phần mềm, hiệu ứng âm thanh, định tuyến và kết hợp; công cụ logic âm nhạc để kiểm soát âm nhạc theo cách tương tác theo thời gian thực; và một thành phần đồng bộ hoá đảm bảo rằng tất cả người chơi trong một phiên đều nghe thấy âm nhạc cùng một lúc, đây là điều kiện tiên quyết để có thể chơi cùng nhau.

Để đạt được mức độ chân thực, chính xác và chất lượng âm thanh cao nhất có thể, chúng tôi đã chọn sử dụng API Âm thanh trên web. Nghiên cứu điển hình này sẽ thảo luận về một số thách thức mà chúng tôi gặp phải và cách chúng tôi giải quyết những thách thức đó. Đã có một số bài viết giới thiệu tuyệt vời tại HTML5Rocks để giúp bạn bắt đầu với Web Audio, vì vậy, chúng ta sẽ chuyển ngay sang phần chuyên sâu.

Viết hiệu ứng âm thanh tuỳ chỉnh

API Âm thanh trên web có một số hiệu ứng hữu ích trong thông số kỹ thuật, nhưng chúng tôi cần có các hiệu ứng phức tạp hơn cho các nhạc cụ của mình trong JAM với Chrome. Ví dụ: có một nút độ trễ gốc trong Web Audio, nhưng có nhiều loại độ trễ – độ trễ âm thanh nổi, độ trễ ping pong, độ trễ slapback và danh sách này còn dài nữa. May mắn là bạn có thể tạo tất cả những hiệu ứng này trong Web Audio bằng cách sử dụng các nút hiệu ứng gốc và một chút trí tưởng tượng.

Vì muốn sử dụng các nút gốc và hiệu ứng tuỳ chỉnh của riêng mình một cách minh bạch nhất có thể, nên chúng tôi quyết định cần tạo một định dạng trình bao bọc có thể đạt được điều này. Các nút gốc trong Web Audio sử dụng phương thức kết nối để liên kết các nút với nhau, vì vậy, chúng ta cần mô phỏng hành vi này. Ý tưởng cơ bản sẽ có dạng như sau:

var MyCustomNode = function(){
    this.input = audioContext.createGain();
    var output = audioContext.createGain();

    this.connect = function(target){
       output.connect(target);
    };
};

Với mẫu này, chúng ta thực sự gần với các nút gốc. Hãy xem cách sử dụng tính năng này.

//create a couple of native nodes and our custom node
var gain = audioContext.createGain(),
    customNode = new MyCustomNode(),
    anotherGain = audioContext.createGain();

//connect our custom node to the native nodes and send to the output
gain.connect(customNode.input);
customNode.connect(anotherGain);
anotherGain.connect(audioContext.destination);
Định tuyến nút tuỳ chỉnh

Điểm khác biệt duy nhất giữa nút tuỳ chỉnh và nút gốc là chúng ta phải kết nối với thuộc tính đầu vào của nút tuỳ chỉnh. Tôi chắc chắn rằng có nhiều cách để giải quyết vấn đề này, nhưng cách này đã đủ gần với mục đích của chúng ta. Bạn có thể phát triển thêm mẫu này để mô phỏng các phương thức ngắt kết nối của AudioNodes gốc, cũng như điều chỉnh đầu vào/đầu ra do người dùng xác định khi kết nối, v.v. Hãy xem thông số kỹ thuật để biết các nút gốc có thể làm gì.

Giờ đây, chúng ta đã có mẫu cơ bản để tạo hiệu ứng tuỳ chỉnh, bước tiếp theo là thực sự cung cấp cho nút tuỳ chỉnh một số hành vi tuỳ chỉnh. Hãy xem nút độ trễ slapback.

Đập lại thật mạnh

Độ trễ slapback (đôi khi gọi là độ trễ slapback echo) là một hiệu ứng cổ điển được dùng trên một số nhạc cụ, từ giọng hát theo phong cách thập niên 50 đến guitar surf. Hiệu ứng này lấy âm thanh đến và phát một bản sao của âm thanh đó với độ trễ nhỏ khoảng 75 đến 250 mili giây. Điều này tạo cảm giác âm thanh bị tát vào mặt, do đó có tên như vậy. Chúng ta có thể tạo hiệu ứng như sau:

var SlapbackDelayNode = function(){
    //create the nodes we'll use
    this.input = audioContext.createGain();
    var output = audioContext.createGain(),
        delay = audioContext.createDelay(),
        feedback = audioContext.createGain(),
        wetLevel = audioContext.createGain();

    //set some decent values
    delay.delayTime.value = 0.15; //150 ms delay
    feedback.gain.value = 0.25;
    wetLevel.gain.value = 0.25;

    //set up the routing
    this.input.connect(delay);
    this.input.connect(output);
    delay.connect(feedback);
    delay.connect(wetLevel);
    feedback.connect(delay);
    wetLevel.connect(output);

    this.connect = function(target){
       output.connect(target);
    };
};
Định tuyến nội bộ của nút slapback

Như một số bạn có thể đã nhận ra, độ trễ này cũng có thể được sử dụng với thời gian trễ lớn hơn, do đó trở thành độ trễ đơn âm thông thường có phản hồi. Sau đây là ví dụ về cách sử dụng độ trễ này để bạn có thể nghe thấy âm thanh.

Định tuyến âm thanh

Khi làm việc với nhiều nhạc cụ và phần nhạc trong các ứng dụng âm thanh chuyên nghiệp, bạn cần có một hệ thống định tuyến linh hoạt để có thể phối và điều chỉnh âm thanh theo cách hiệu quả. Trong JAM với Chrome, chúng tôi đã phát triển một hệ thống bus âm thanh, tương tự như các hệ thống bus âm thanh trong bảng điều khiển trộn thực tế. Điều này cho phép chúng ta kết nối tất cả các nhạc cụ cần hiệu ứng âm vang với một bus hoặc kênh chung, sau đó thêm hiệu ứng âm vang vào bus đó thay vì thêm hiệu ứng âm vang vào từng nhạc cụ riêng lẻ. Đây là một hoạt động tối ưu hoá lớn và bạn nên làm điều tương tự ngay khi bắt đầu làm các ứng dụng phức tạp hơn.

Định tuyến AudioBus

Rất may, bạn có thể dễ dàng thực hiện việc này trong Web Audio. Về cơ bản, chúng ta có thể sử dụng khung đã xác định cho các hiệu ứng và sử dụng khung đó theo cách tương tự.

var AudioBus = function(){
    this.input = audioContext.createGain();
    var output = audioContext.createGain();

    //create effect nodes (Convolver and Equalizer are other custom effects from the library presented at the end of the article)
    var delay = new SlapbackDelayNode(),
        convolver = new tuna.Convolver(),
        equalizer = new tuna.Equalizer();

    //route 'em
    //equalizer -> delay -> convolver
    this.input.connect(equalizer);
    equalizer.connect(delay.input);
    delay.connect(convolver);
    convolver.connect(output);

    this.connect = function(target){
       output.connect(target);
    };
};

Bạn có thể sử dụng như sau:

//create some native oscillators and our custom audio bus
var bus = new AudioBus(),
    instrument1 = audioContext.createOscillator(),
    instrument2 = audioContext.createOscillator(),
    instrument3 = audioContext.createOscillator();

//connect our instruments to the same bus
instrument1.connect(bus.input);
instrument2.connect(bus.input);
instrument3.connect(bus.input);
bus.connect(audioContext.destination);

Và thế là xong, chúng ta đã áp dụng hiệu ứng độ trễ, cân bằng và âm vang (là một hiệu ứng khá tốn kém về hiệu suất) với chi phí chỉ bằng một nửa so với khi áp dụng hiệu ứng cho từng nhạc cụ riêng lẻ. Nếu muốn thêm một số gia vị khác vào bus, chúng ta có thể thêm hai nút tăng cường mới – preGain và postGain – cho phép chúng ta tắt hoặc làm mờ âm thanh trong bus theo hai cách khác nhau. preGain được đặt trước các hiệu ứng và postGain được đặt ở cuối chuỗi. Sau đó, nếu chúng ta làm mờ preGain, các hiệu ứng vẫn sẽ cộng hưởng sau khi gain đạt đến mức thấp nhất, nhưng nếu chúng ta làm mờ postGain, tất cả âm thanh sẽ bị tắt cùng một lúc.

Tiếp theo nên làm gì?

Bạn có thể và nên phát triển thêm các phương thức mà tôi đã mô tả ở đây. Bạn có thể/nên triển khai các tính năng như đầu vào và đầu ra của các nút tuỳ chỉnh cũng như các phương thức kết nối bằng cách sử dụng tính năng kế thừa dựa trên nguyên mẫu. Xe buýt phải có thể tạo hiệu ứng một cách linh động bằng cách truyền danh sách hiệu ứng.

Để kỷ niệm việc phát hành JAM với Chrome, chúng tôi đã quyết định tạo khung hiệu ứng nguồn mở. Nếu phần giới thiệu ngắn gọn này đã thu hút bạn, vui lòng xem và đóng góp. Chúng tôi đang thảo luận tại đây về việc chuẩn hoá định dạng cho các thực thể Âm thanh trên web tuỳ chỉnh. Tham gia ngay