Örnek olay - Chrome JAM

Sesleri nasıl mükemmel hale getirdik?

Oskar Eriksson
Oskar Eriksson

Giriş

JAM with Chrome, Google tarafından oluşturulan web tabanlı bir müzik projesidir. Chrome ile JAM, dünyanın her yerinden kullanıcıların grup oluşturmasına ve tarayıcıda gerçek zamanlı olarak jam yapmasına olanak tanır. DinahMoe olarak bu projenin bir parçası olmaktan büyük mutluluk duyuyoruz. Görevimiz, uygulama için müzik üretmek ve müzik bileşenini tasarlayıp geliştirmekti. Geliştirme üç ana alandan oluşuyordu: MIDI oynatma, yazılım örnekleyiciler, ses efektleri, yönlendirme ve miksleme içeren bir "müzik iş istasyonu"; müziği gerçek zamanlı olarak etkileşimli bir şekilde kontrol eden bir müzik mantığı motoru; ve birlikte çalınabilmesi için ön koşul olan, oturumdaki tüm oyuncuların müziği tam olarak aynı anda duymasını sağlayan bir senkronizasyon bileşeni.

Mümkün olan en yüksek düzeyde özgünlük, doğruluk ve ses kalitesi elde etmek için Web Audio API'yi kullanmayı tercih ettik. Bu örnek olayda, karşılaştığımız bazı zorluklardan ve bunları nasıl çözdüğümüzden bahsedeceğiz. HTML5Rocks'ta Web Audio'yu kullanmaya başlamanıza yardımcı olacak birçok mükemmel giriş makalesi zaten mevcut. Bu nedenle, doğrudan konuya gireceğiz.

Özel ses efektleri yazma

Web Audio API'de spesifikasyona dahil edilmiş çeşitli kullanışlı efektler vardır ancak Chrome ile JAM'deki enstrümanlarımız için daha ayrıntılı efektlere ihtiyacımız vardı. Örneğin, Web Audio'da yerel bir gecikme düğümü vardır ancak stereo gecikme, ping pong gecikme, slapback gecikme ve daha birçok gecikme türü vardır. Neyse ki bunların tümü, yerel efekt düğümleri ve biraz hayal gücü kullanılarak Web Audio'da oluşturulabilir.

Yerel düğümleri ve kendi özel efektlerimizi mümkün olduğunca şeffaf bir şekilde kullanabilmek istediğimizden, bunu başarabilecek bir sarmalayıcı biçimi oluşturmamız gerektiğine karar verdik. Web Audio'daki yerel düğümler, düğümleri birbirine bağlamak için bağlantı yöntemini kullanır. Bu nedenle, bu davranışı taklit etmemiz gerekiyordu. Temel fikir şu şekildedir:

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

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

Bu kalıpla, yerel düğümlere gerçekten çok yakınız. Bunun nasıl kullanılacağına bakalım.

//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);
Özel düğümü yönlendirme

Özel düğümümüz ile yerel düğüm arasındaki tek fark, özel düğümlerin giriş özelliğine bağlanmamız gerektiğidir. Bu sorunun etrafından dolaşmanın yolları vardır ancak amacımıza ulaşmak için bu yöntem yeterliydi. Bu kalıp, yerel AudioNode'ların bağlantı kesme yöntemlerini simüle etmek ve bağlanırken kullanıcı tanımlı giriş/çıkışları barındırmak için daha da geliştirilebilir. Yerel düğümlerin neler yapabileceğini görmek için özelliğe göz atın.

Özel efektler oluşturmak için temel kalıbımız hazır olduğunda, bir sonraki adımda özel düğüme özel bir davranış kazandırmamız gerekiyordu. Bir slapback gecikme düğümüne bakalım.

Slapback

Bazen slapback yankı olarak da adlandırılan slapback gecikmesi, 50'ler tarzı vokallerden surf gitarlarına kadar birçok enstrümanda kullanılan klasik bir efekttir. Bu efekt, gelen sesi alır ve sesin bir kopyasını yaklaşık 75-250 milisaniyelik hafif bir gecikmeyle çalar. Bu, sesin geri itildiği hissini verir. Bu efekti şu şekilde oluşturabiliriz:

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);
    };
};
Slapback düğümünün dahili yönlendirmesi

Bazılarınızın fark etmiş olabileceği gibi, bu gecikme daha uzun gecikme süreleriyle de kullanılabilir ve böylece geri bildirim içeren normal bir mono gecikme haline gelebilir. Bu gecikmenin nasıl ses çıkardığını duymanız için aşağıda bir örnek verilmiştir.

Ses yönlendirme

Profesyonel ses uygulamalarında farklı enstrüman ve müzik parçalarıyla çalışırken sesleri etkili bir şekilde karıştırmanıza ve modüle etmenize olanak tanıyan esnek bir yönlendirme sistemine sahip olmanız gerekir. Chrome ile JAM'de, fiziksel miks konsollarında bulunanlara benzer bir ses veri yolu sistemi geliştirdik. Bu sayede, yankı efekti gerektiren tüm enstrümanları ortak bir otobüse veya kanala bağlayabilir ve her enstrümana ayrı ayrı yankı eklemek yerine yankıyı bu otobüse ekleriz. Bu önemli bir optimizasyondur ve daha karmaşık uygulamalar yapmaya başladığınızda benzer bir işlem yapmanız önerilir.

AudioBus'un yönlendirilmesi

Neyse ki bunu Web Audio'da gerçekleştirmek çok kolaydır. Temel olarak, efektler için tanımladığımız iskeleti aynı şekilde kullanabiliriz.

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

Bu, aşağıdaki gibi kullanılır:

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

İşte bu kadar. Gecikme, eşitleme ve yankı (performans açısından oldukça pahalı bir efekttir) efektlerini her enstrümana ayrı ayrı uygularmış gibi yarı maliyetle uyguladık. Otobüse biraz daha lezzet katmak isterseniz iki yeni kazanç düğümü (ön kazanç ve son kazanç) ekleyebilirsiniz. Bu düğümler, otobüsteki sesleri iki farklı şekilde kapatmanıza veya seslerini azaltmanıza olanak tanır. preGain, efektlerin önüne, postGain ise zincirin sonuna yerleştirilir. Ardından, ön kazancı azaltırsak kazanç en alt seviyeye ulaşsa bile efektler yankılanmaya devam eder. Ancak, son kazancı azaltırsak tüm sesler aynı anda kapatılır.

Bundan sonra ne yapmalıyız?

Burada açıklanan yöntemler daha da geliştirilebilir ve geliştirilmelidir. Özel düğümlerin giriş ve çıkışı ve bağlantı yöntemleri gibi öğeler, prototip tabanlı devralma kullanılarak uygulanabilir/uygulanmalıdır. Otobüsler, efekt listesi iletilerek dinamik olarak efekt oluşturabilmelidir.

JAM with Chrome'un kullanıma sunulmasını kutlamak için etki çerçevemizi açık kaynak olarak kullanıma sunmaya karar verdik. Bu kısa tanıtım ilginizi çektiyse lütfen göz atın ve katkıda bulunun. Özel Web Audio öğeleri için bir biçimi standartlaştırmayla ilgili buradaki tartışmaya göz atabilirsiniz. Harekete geçin!