Studi kasus - JAM dengan Chrome

Cara kami membuat audio rock

Oskar Eriksson
Oskar Eriksson

Pengantar

JAM dengan Chrome adalah proyek musik berbasis web yang dibuat oleh Google. JAM dengan Chrome memungkinkan orang dari seluruh dunia membentuk band dan menikmati musik secara real time di dalam browser. Kami di DinahMoe sangat senang menjadi bagian dari proyek ini. Kami berperan dalam memproduksi musik untuk aplikasi, mendesain, dan mengembangkan komponen musik. Pengembangan ini terdiri dari tiga area utama: "workstation musik" yang mencakup pemutaran midi, sampel software, efek audio, pemilihan rute, dan mixing; mesin logika musik untuk mengontrol musik secara interaktif secara real-time; serta komponen sinkronisasi yang memastikan bahwa semua pemain dalam sebuah sesi mendengar musik pada waktu yang sama persis, sebagai prasyarat untuk dapat bermain bersama.

Untuk mencapai tingkat autentisitas, akurasi, dan kualitas audio setinggi mungkin, kami memilih untuk menggunakan Web Audio API. Studi kasus ini akan membahas beberapa tantangan yang dihadapi, dan cara kita menyelesaikannya. Sudah ada sejumlah artikel pengantar yang bagus di HTML5Rocks untuk membantu Anda memulai Web Audio, jadi kita akan langsung menuju ke ujung kumpulan yang paling dalam.

Menulis efek audio kustom

Web Audio API memiliki sejumlah efek berguna yang disertakan dalam spesifikasi, tetapi kami membutuhkan efek yang lebih rumit untuk instrumen kami dalam JAM dengan Chrome. Misalnya, ada node penundaan native di Audio Web, tetapi ada banyak jenis penundaan - penundaan stereo, penundaan ping-pong, penundaan slapback, dan masih banyak lagi. Untungnya, semua itu dapat dibuat di Audio Web menggunakan simpul efek asli dan sedikit imajinasi.

Karena kami ingin dapat menggunakan node native dan efek kustom kami sendiri setransparan mungkin, kami memutuskan bahwa kami perlu membuat format wrapper yang dapat mencapai hal ini. Node native di Web Audio menggunakan metode penghubung untuk menautkan node bersama-sama, jadi kita perlu mengemulasikan perilaku ini. Seperti inilah ide dasarnya:

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

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

Dengan pola ini, kita sangat dekat dengan node native. Mari kita lihat bagaimana ini akan digunakan.

//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);
Merutekan node kustom

Satu-satunya perbedaan antara node kustom dan yang native adalah kita harus terhubung ke properti input node kustom. Kami yakin ada cara untuk menghindarinya, tetapi ini sudah cukup untuk tujuan kita. Pola ini dapat dikembangkan lebih lanjut untuk menyimulasikan metode pemutusan koneksi AudioNodes native, serta mengakomodasi input/output yang ditetapkan pengguna saat menghubungkan, dan seterusnya. Lihat spesifikasi untuk melihat apa yang dapat dilakukan node native.

Setelah kita memiliki pola dasar untuk membuat efek kustom, langkah berikutnya adalah memberikan beberapa perilaku kustom pada node kustom. Mari kita lihat node penundaan slapback.

Santai aja gak sih?

Slapback delay, terkadang disebut slapback echo, adalah efek klasik yang digunakan pada sejumlah instrumen, mulai dari vokal bergaya 50-an hingga gitar selancar. Efeknya mengambil suara yang masuk dan memutar salinan suara dengan sedikit penundaan sekitar 75-250 milidetik. Hal ini memberi kesan suara dipukul kembali, dan ini namanya. Kita dapat membuat efek seperti ini:

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);
    };
};
Pemilihan rute internal node slapback

Seperti yang mungkin sudah Anda sadari, penundaan ini juga dapat digunakan dengan waktu tunda yang lebih besar, dan dengan demikian menjadi penundaan mono yang biasa dengan masukan. Berikut ini contoh yang menggunakan penundaan ini agar Anda bisa mendengar seperti apa kedengarannya.

Audio pemilihan rute

Saat menggunakan berbagai instrumen dan bagian musik dalam aplikasi audio profesional, sangat penting untuk memiliki sistem pemilihan rute yang fleksibel yang memungkinkan Anda mencampur dan memodulasi suara dengan cara yang efektif. Pada JAM dengan Chrome, kami telah mengembangkan sistem bus audio, mirip dengan yang ditemukan di mixing board fisik. Dengan begitu, kita dapat menghubungkan semua instrumen yang memerlukan efek reverb ke bus atau channel yang sama, lalu menambahkan gema ke bus tersebut, bukan menambahkan gema ke setiap instrumen yang terpisah. Ini adalah pengoptimalan besar dan disarankan untuk melakukan hal serupa segera setelah Anda mulai melakukan aplikasi yang lebih kompleks.

Merutekan AudioBus

Untungnya ini sangat mudah dicapai di Audio Web. Pada dasarnya kita bisa menggunakan kerangka yang kita tentukan untuk efek dan menggunakannya dengan cara yang sama.

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);
    };
};

Ini akan digunakan seperti ini:

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

Dan baiklah, kita telah menerapkan penundaan, penyetaraan, dan gaung (yang efeknya agak mahal, lebih mengutamakan performa) dengan setengah biaya seolah-olah kita telah menerapkan efek ke setiap instrumen terpisah. Jika ingin menambahkan sedikit tambahan ke bus, kita dapat menambahkan dua node perolehan baru - preGain dan postGain - yang akan memungkinkan kita menonaktifkan atau memudar suara dalam bus dengan dua cara berbeda. preGain ditempatkan sebelum efek, dan postGain ditempatkan di akhir rantai. Jika kita kemudian memudar, efek preGain masih akan diterima setelah penguatan mencapai bawah, tetapi jika kita memudar postGain, semua suara akan dibisukan pada saat yang sama.

Mau ke mana?

Metode yang saya jelaskan di sini dapat, dan seharusnya, dikembangkan lebih lanjut. Hal-hal seperti input dan output node kustom, serta metode koneksi, dapat/harus diimplementasikan menggunakan pewarisan berbasis prototipe. Bus harus dapat membuat efek secara dinamis dengan meneruskan daftar efek.

Untuk merayakan rilis JAM dengan Chrome, kami telah memutuskan untuk menjadikan framework efek kami sebagai open source. Jika perkenalan singkat ini membuat Anda tertarik, silakan lihat dan jangan ragu untuk berkontribusi. Ada diskusi yang berlangsung di sini terkait standardisasi format untuk minat Audio Web kustom. Libatkan diri Anda!