Örnek olay - Chrome JAM

Sesi nasıl mükemmel hale getirdik?

Oskar Eriksson
Oskar Eriksson

Giriş

Chrome JAM, Google tarafından oluşturulmuş, web tabanlı bir müzikal projedir. Chrome JAM, dünyanın her yerinden kullanıcıların tarayıcı içinde gerçek zamanlı olarak bir kayış oluşturup müzik çalmasına olanak tanır. DinahMoe olarak bu projenin parçası olmaktan büyük memnuniyet duyduk. Bizim görevimiz, uygulama için müzik üretmek ve müzik bileşenini tasarlayıp geliştirmekti. Bu geliştirme süreci üç ana alandan oluşmaktadır: Midi çalma, yazılım tanıtıcıları, ses efektleri, yönlendirme ve miksleme dahil bir "müzik iş istasyonu"; müziği gerçek zamanlı olarak etkileşimli olarak kontrol etmek için bir müzik mantığı motoru ve bir oturumdaki tüm oyuncuların, müziği tam olarak aynı anda duymasını sağlayan bir senkronizasyon bileşeni; birlikte çalışabilmek için bir ön koşuldur.

Mümkün olan en yüksek özgünlük, doğruluk ve ses kalitesini elde etmek için Web Audio API'sını kullanmayı tercih ettik. Bu örnek olay incelemesinde, karşılaştığımız bazı zorluklar ve bunları nasıl çözdüğümüz üzerinde durulacak. HTML5Rocks'ta Web Audio'yu kullanmaya başlamanıza yardımcı olacak bir dizi harika tanıtım makalesi zaten var. Dolayısıyla biz de doğrudan havuzun en sonuna geçeceğiz.

Özel ses efektleri yazma

Web Audio API'sı, spesifikasyonda pek çok yararlı efekt içeriyordu, ancak Chrome JAM'deki araçlarımız için daha ayrıntılı efektlere ihtiyacımız vardı. Örneğin, Web Sesi'nde yerel bir gecikme düğümü vardır, ancak birçok gecikme türü vardır: stereo gecikme, ping pong gecikmesi, uzun video kaydı gecikmesi ve liste bu şekilde devam eder. Neyse ki bunların tümü, yerel efekt düğümlerini ve biraz hayal gücünü kullanarak Web Audio'da oluşturmak mümkündür.

Yerel düğümleri ve kendi özel efektlerimizi mümkün olduğunca şeffaf bir şekilde kullanabilmek istediğimizden, bunu başarabilen 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ışı emüle 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 modelle yerel düğümlere çok yaklaştık. Şimdi bunun nasıl kullanıldığı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önlendiriliyor

Özel düğümümüzle yerel düğüm arasındaki tek fark, özel düğüm giriş özelliğine bağlanmamızın gerekmesidir. Bunu atlatmanın farklı yolları olduğundan eminim ama bu yöntem bizim amaçlarımıza çok yakındı. Bu kalıp, yerel AudioNode'ların bağlantı kesme yöntemlerini simüle etmenin yanı sıra bağlantı ve benzeri işlemler sırasında kullanıcı tanımlı giriş/çıkışlara uyum sağlamak için daha da geliştirilebilir. Yerel düğümlerin neler yapabileceğini görmek için spesifikasyona göz atın.

Artık özel efektler oluşturmak için temel kalıbımıza sahip olduğumuza göre, bir sonraki adım özel düğüme bir miktar özel davranış vermekti. Bir kapatma gecikmesi düğümüne göz atalım.

Aynen öyle güldür

Slapback Gecikmesi, bazen toparlama yankısı olarak da adlandırılır ve 50'lerin tarzı vokallerden sörf gitarlarına kadar birçok enstrümanda kullanılan klasik bir efekttir. Efekt gelen sesi alır ve yaklaşık 75-250 milisaniyelik bir gecikmeyle sesin bir kopyasını çalar. Bu, sesin arkaya vurulduğu hissi, dolayısıyla ad veriyor. 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 zaten fark etmiş olabileceği gibi, bu gecikme daha uzun gecikme sürelerinde de kullanılabilir ve böylece geri bildirimle düzenli bir mono gecikmesi haline gelebilir. Sesin nasıl duyulduğunu duymanız için bu gecikmenin kullanıldığı bir örneği aşağıda bulabilirsiniz.

Ses yönlendirme

Profesyonel ses uygulamalarında farklı enstrümanlarla ve müzik parçalarıyla çalışırken, sesleri etkili yöntemlerle karıştırıp düzenlemenizi sağlayan esnek bir yönlendirme sistemine sahip olmanız çok önemlidir. Chrome JAM'de, fiziksel miksaj sistemlerinde bulunanlara benzer bir ses yolu sistemi geliştirdik. Böylece yankı efekti gerektiren tüm enstrümanları yaygın bir otobüse veya kanala bağlayabilir, her enstrümana yankı eklemek yerine yankıyı bu otobüse ekleyebiliriz. Bu büyük bir optimizasyondur ve daha karmaşık uygulamalar yapmaya başlar başlamaz benzer bir şey yapmanız önemle tavsiye edilir.

AudioBus'ın Rotalanması

Neyse ki Web Audio'da bunu başarmak çok kolay. Efektler için tanımladığımız iskeleti kullanabilir ve 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, şu şekilde 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 oldu, gecikme, dengeleme ve yankıyı (bu etkiler performans açısından oldukça pahalı bir etkendir), efektleri her bir ayrı araca uygulamışız gibi maliyetin yarısına uyguladık. Otobüse biraz renk katmak istersek, otobüste sesleri iki farklı şekilde kapatmamıza veya azaltmamıza olanak tanıyan iki yeni kazanç düğümü (preGain ve postGain) ekleyebilirdik. PreGain, efektlerin önüne, postGain ise zincirin sonuna yerleştiriliyor. Ardından, preGain'in sesi azaltılırsa, ses kazancı en alt seviyeye ulaştıktan sonra da sesler yankılanmaya devam eder. Ancak postGain sesi kaybolursa tüm sesler aynı anda kısılır.

Nereden nereye?

Burada açıkladığım bu yöntemler daha da geliştirilebilir ve geliştirilmeli. Özel düğümlerin giriş ve çıkışları ile bağlantı yöntemleri gibi şeyler, prototipe dayalı devralma kullanılarak uygulanabilir/uygulanmalıdır. Otobüsler, efekt listesi iletilerek dinamik olarak efekt oluşturabilmelidir.

Chrome JAM'in kullanıma sunulmasını kutlamak için efektler çerçevemizi açık kaynak yapmaya karar verdik. Bu kısa tanıtım hoşunuza gittiyse lütfen göz atın ve katkıda bulunmaktan çekinmeyin. Özel Web Sesi alanlarına yönelik bir biçimin standartlaştırılmasıyla ilgili konuyu burada bulabilirsiniz. Harekete geçin!