Studi kasus - JAM dengan Chrome

Cara kami membuat audio yang keren

Oskar Eriksson
Oskar Eriksson

Pengantar

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

Untuk mencapai tingkat keaslian, akurasi, dan kualitas audio setinggi mungkin, kami memilih untuk menggunakan Web Audio API. Studi kasus ini akan membahas beberapa tantangan yang kami hadapi, dan cara kami mengatasinya. Di HTML5Rocks, sudah ada sejumlah artikel pengantar yang bagus untuk membantu Anda memulai Web Audio, jadi kita akan langsung masuk ke bagian yang lebih dalam.

Menulis efek audio kustom

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

Karena kami ingin dapat menggunakan node native dan efek kustom kami sendiri dengan cara yang setransparan mungkin, kami memutuskan bahwa kami perlu membuat format wrapper yang dapat mencapai hal ini. Node native di Web Audio menggunakan metode koneksinya untuk menautkan node bersama-sama, sehingga kita perlu mengemulasi perilaku ini. Berikut adalah tampilan 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 cara penggunaannya.

//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 node native adalah kita harus terhubung ke properti input node kustom. Saya yakin ada cara untuk mengakalinya, tetapi ini cukup dekat dengan tujuan kita. Pola ini dapat dikembangkan lebih lanjut untuk menyimulasikan metode pemutusan koneksi AudioNode native, serta mengakomodasi input/output yang ditentukan pengguna saat terhubung dan sebagainya. Lihat spesifikasi untuk mengetahui kemampuan node native.

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

Slapback seperti yang Anda inginkan

Delay slapback, yang terkadang disebut echo slapback, adalah efek klasik yang digunakan pada sejumlah instrumen, mulai dari vokal gaya tahun 50-an hingga gitar surf. Efek ini mengambil suara yang masuk dan memutar salinan suara dengan sedikit penundaan sekitar 75-250 milidetik. Hal ini memberikan kesan suara yang ditampar, sehingga 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 penundaan yang lebih besar, sehingga menjadi penundaan mono reguler dengan masukan. Berikut adalah contoh penggunaan penundaan ini agar Anda dapat mendengar suaranya.

Pemilihan rute audio

Saat menggunakan berbagai instrumen dan bagian musik dalam aplikasi audio profesional, Anda harus memiliki sistem pemilihan rute yang fleksibel yang memungkinkan Anda mencampur dan memodulasi suara dengan cara yang efektif. Di JAM dengan Chrome, kami telah mengembangkan sistem bus audio, mirip dengan yang ditemukan di papan mixing fisik. Hal ini memungkinkan kita menghubungkan semua instrumen yang memerlukan efek reverb ke bus atau saluran umum, lalu menambahkan reverb ke bus tersebut, bukan menambahkan reverb ke setiap instrumen terpisah. Ini adalah pengoptimalan utama dan sangat disarankan untuk melakukan hal serupa segera setelah Anda mulai melakukan aplikasi yang lebih kompleks.

Pemilihan rute AudioBus

Untungnya, hal ini sangat mudah dilakukan di Web Audio. Pada dasarnya, kita dapat menggunakan kerangka yang telah ditentukan 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, selesai. Kita telah menerapkan delay, equalisasi, dan reverb (yang merupakan efek yang agak mahal, dari segi performa) dengan biaya setengahnya seolah-olah kita telah menerapkan efek tersebut ke setiap instrumen yang terpisah. Jika ingin menambahkan beberapa bumbu tambahan ke bus, kita dapat menambahkan dua node gain baru - preGain dan postGain - yang memungkinkan kita menonaktifkan atau memudarkan suara di bus dengan dua cara berbeda. preGain ditempatkan sebelum efek, dan postGain ditempatkan di akhir rantai. Jika kita memudar preGain, efeknya akan tetap beresonansi setelah gain mencapai bagian bawah, tetapi jika kita memudar postGain, semua suara akan dibisukan secara bersamaan.

Ke mana kita akan pergi dari sini?

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

Untuk merayakan rilis JAM dengan Chrome, kami memutuskan untuk menjadikan framework efek kami open source. Jika pengantar singkat ini menarik minat Anda, lihat dan jangan ragu untuk berkontribusi. Ada diskusi yang sedang berlangsung di sini terkait standarisasi format untuk entitas Audio Web kustom. Libatkan diri Anda!