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 Rock

Oskar Eriksson
Oskar Eriksson

Giới thiệu

JAM with Chrome là một dự án âm nhạc dựa trên nền tảng web do Google tạo ra. ỨNG TÁC với Chrome cho phép mọi người từ khắp nơi trên thế giới tạo thành một ban nhạc và hoà nhạc theo thời gian thực trong trình duyệt. Chúng tôi tại DinahMoe rất vui khi được tham gia dự án này. Công việc của chúng tôi là sản xuất nhạc cho ứng dụng này, 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 ba lĩnh vực chính: "máy trạm âm nhạc" bao gồm phát nhạc giữa các lần tải, bộ lấy mẫu phần mềm, hiệu ứng âm thanh, định tuyến và phối nhạc; công cụ logic âm nhạc để điều khiển nhạc tương tác theo thời gian thực; và thành phần đồng bộ hoá đảm bảo rằng tất cả người chơi trong phiên nghe nhạc chính xác cùng một lúc, đ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 Web âm thanh. 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 làm quen với Web Audio, vì vậy chúng ta sẽ đi thẳng vào phần tìm hiểu sâu hơn.

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

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

Vì muốn có thể sử dụng nút gốc và hiệu ứng tuỳ chỉnh của riêng mình theo 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 Âm thanh web 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 là 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ự rất gần với các nút gốc. Hãy xem cách sử dụng.

//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

Sự 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 có nhiều cách để tránh né điều này, nhưng cách này cũng đủ phù hợp với mục đích của chúng ta. Mẫu này có thể được phát triển thêm để mô phỏng các phương thức ngắt kết nối của AudioNode gốc, cũng như đáp ứng đầ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 những việc nút gốc có thể làm.

Giờ đây, khi 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 cùng xem nút trễ slapback.

Thả hồn như ý bạn

Độ trễ slapback, đôi khi được gọi là tiếng vang slapback, là hiệu ứng cổ điển được sử dụng trên một số nhạc cụ, từ giọng hát của những năm 50 cho đến ghi-ta lướt sóng. Hiệu ứng này lấy âm thanh đến và phát một bản sao của âm thanh có độ trễ ngắn khoảng 75 đến 250 mili giây. Điều này mang đến cảm giác âm thanh bị đẩy lại, chính là tên gọi của công cụ nà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ộ nút slapback

Có thể một số bạn đã nhận ra rằng độ trễ này cũng có thể xảy ra với độ trễ lớn hơn, dẫn đến độ trễ đơn âm thông thường khi có phản hồi. Sau đây là một ví dụ về việc sử dụng độ trễ này để cho bạn nghe âm thanh của âm thanh.

Định tuyến âm thanh

Khi thao tác với nhiều loại nhạc cụ và bộ phận âm nhạc trong các ứng dụng âm thanh chuyên nghiệp, điều cần thiết là phải có một hệ thống định tuyến linh hoạt để có thể phối và điều chỉnh âm thanh một cách hiệu quả. Trong ỨNG TÁC với Chrome, chúng tôi đã phát triển một hệ thống bus âm thanh, tương tự như hệ thống được tìm thấy trong các bảng điều khiển vật lý. Đ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, rồi thêm âm vang vào bus đó thay vì thêm hồi âm cho từng nhạc cụ riêng biệt. Đây là một hoạt động tối ưu hoá lớn và bạn nên thực hiện tương tự ngay khi bắt đầu thực hiện các ứng dụng phức tạp hơn.

Định tuyến AudioBus

Thật may là điều này thực sự dễ dàng đạt được trong Âm thanh web. Về cơ bản, chúng ta có thể sử dụng bộ xương đã xác định cho các hiệu ứng và sử dụng nó 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);
    };
};

Hàm này sẽ được 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à voila, chúng tôi đã áp dụng độ trễ, cân bằng âm thanh và hiệu ứng dội lại (đây là một hiệu ứng khá tốn kém, nhưng xét về hiệu suất) với chi phí chỉ bằng một nửa, tương tự như khi áp dụng hiệu ứng cho từng công cụ riêng biệt. Nếu muốn thêm chút gia vị cho xe buýt, chúng ta có thể thêm hai nút tăng âm lượng mới - pre trung và Post tăng - cho phép chúng ta tắt hoặc làm mờ âm thanh trong xe buýt theo hai cách khác nhau. Độ lợi ích được đặt trước hiệu ứng và độ lợi sau được đặt vào cuối chuỗi. Nếu sau đó chúng ta làm mờ trước, các hiệu ứng vẫn sẽ cộng hưởng sau khi mức tăng âm đã đạt đến mức dưới cùng nhưng nếu chúng ta làm mờ sau khi tăng âm, tất cả âm thanh sẽ bị tắt cùng một lúc.

Bạn muốn đi từ đâu?

Các phương pháp này mà tôi mô tả ở đây có thể và nên được phát triển thêm. Bạn có thể/nên triển khai các yếu tố như đầu vào và đầu ra của nút tuỳ chỉnh, cũng như phương thức kết nối bằng cách sử dụng tính 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 hoạt bằng cách truyền danh sách các hiệu ứng.

Để kỷ niệm bản phát hành ỨNG TÁC với Chrome, chúng tôi đã quyết định cung cấp khung gồm các hiệu ứng dạng nguồn mở. Nếu phần giới thiệu ngắn gọn này làm bạn thích thú, vui lòng xem và đóng góp cho chúng tôi. Có một cuộc thảo luận đang diễn ra tại đây về việc chuẩn hoá một định dạng cho nội dung Âm thanh web tuỳ chỉnh. Tham gia ngay