Web Audio API'sını kullanmaya başlama

Boris Smus
Boris Smus

HTML5 <audio> öğesinden önce, web'in sessizliğini bozmak için Flash veya başka bir eklenti gerekiyordu. Web'de ses için artık eklenti gerekmiyor ancak ses etiketi, gelişmiş oyunların ve etkileşimli uygulamaların uygulanması konusunda önemli sınırlamalar getiriyor.

Web Audio API, web uygulamalarında ses işlemek ve sentezlemek için kullanılan üst düzey bir JavaScript API'sidir. Bu API'nin amacı, modern oyun ses motorlarında bulunan özelliklerin yanı sıra modern masaüstü ses prodüksiyonu uygulamalarında bulunan bazı miksaj, işleme ve filtreleme görevlerini içermektir. Aşağıda, bu güçlü API'nin kullanımına ilişkin kısa bir giriş yer almaktadır.

AudioContext'i kullanmaya başlama

AudioContext, tüm sesleri yönetmek ve çalmak için kullanılır. Web Audio API'yi kullanarak ses üretmek için bir veya daha fazla ses kaynağı oluşturun ve bunları AudioContext örneği tarafından sağlanan ses hedefine bağlayın. Bu bağlantının doğrudan olması gerekmez ve ses sinyali için işleme modülleri olarak işlev gören herhangi bir sayıda ara AudioNodes üzerinden geçebilir. Bu yönlendirme, Web Audio spesifikasyonunda daha ayrıntılı olarak açıklanmaktadır.

AudioContext'nın tek bir örneği birden fazla ses girişini ve karmaşık ses grafiklerini destekleyebilir. Bu nedenle, oluşturduğumuz her ses uygulaması için bunlardan yalnızca birine ihtiyacımız olacaktır.

Aşağıdaki snippet, bir AudioContext oluşturur:

var context;
window.addEventListener('load', init, false);
function init() {
    try {
    context = new AudioContext();
    }
    catch(e) {
    alert('Web Audio API is not supported in this browser');
    }
}

Daha eski WebKit tabanlı tarayıcılarda webkit önekini kullanın (ör. webkitAudioContext).

AudioNode oluşturma ve ses dosyası verilerini kod çözme gibi Web Audio API'nin birçok ilginç işlevi AudioContext yöntemidir.

Sesler yükleniyor

Web Audio API, kısa ve orta uzunluktaki sesler için AudioBuffer kullanır. Temel yaklaşım, ses dosyalarını getirmek için XMLHttpRequest kullanmaktır.

API, ses dosyası verilerinin WAV, MP3, AAC, OGG ve diğerleri gibi birden fazla biçimde yüklenmesini destekler. Farklı ses biçimleri için tarayıcı desteği değişir.

Aşağıdaki snippet, ses örneği yüklemeyi gösterir:

var dogBarkingBuffer = null;
var context = new AudioContext();

function loadDogSound(url) {
    var request = new XMLHttpRequest();
    request.open('GET', url, true);
    request.responseType = 'arraybuffer';

    // Decode asynchronously
    request.onload = function() {
    context.decodeAudioData(request.response, function(buffer) {
        dogBarkingBuffer = buffer;
    }, onError);
    }
    request.send();
}

Ses dosyası verileri ikili (metin değil) olduğundan isteğin responseType değerini 'arraybuffer' olarak ayarlıyoruz. ArrayBuffers hakkında daha fazla bilgi için XHR2 ile ilgili bu makaleye göz atın.

(Kodlanmamış) ses dosyası verileri alındıktan sonra daha sonra kod çözme işlemi için saklanabilir veya AudioContext decodeAudioData() yöntemi kullanılarak hemen kod çözme işlemi yapılabilir. Bu yöntem, request.response içinde depolanan ses dosyası verilerinin ArrayBuffer alır ve bunları eşzamansız olarak (ana JavaScript yürütme iş parçacığını engellemeden) kodunu çözer.

decodeAudioData() işlemi tamamlandığında, çözülmüş PCM ses verilerini AudioBuffer olarak sağlayan bir geri çağırma işlevi çağrılır.

Ses çalma

Bir veya daha fazla AudioBuffers yüklendikten sonra ses çalmaya hazırız. AudioBuffer adlı dosyaya köpek havlaması sesi yüklediğimizi ve yüklemenin tamamlandığını varsayalım. Ardından, aşağıdaki kodla bu arabelleği oynatabiliriz.

var context = new AudioContext();

function playSound(buffer) {
    var source = context.createBufferSource(); // creates a sound source
    source.buffer = buffer;                    // tell the source which sound to play
    source.connect(context.destination);       // connect the source to the context's destination (the speakers)
    source.noteOn(0);                          // play the source now
}

Bu playSound() işlevi, bir tuşa her basıldığında veya fareyle bir şey tıklandığında çağrılabilir.

noteOn(time) işlevi, oyunlar ve zamana duyarlı diğer uygulamalar için hassas ses çalma zamanlaması yapmayı kolaylaştırır. Ancak bu planlamanın düzgün çalışması için ses arabelleklerinizin önceden yüklendiğinden emin olun.

Web Audio API'yi özetleme

Elbette, bu sesi yüklemek için sabit kodlanmamış daha genel bir yükleme sistemi oluşturmak daha iyi olur. Bir ses uygulamasının veya oyunun kullanacağı kısa ila orta uzunluktaki birçok sesle başa çıkmak için çeşitli yaklaşımlar vardır. İşte BufferLoader (web standardının bir parçası değildir) kullanarak bu yaklaşımlardan birini uygulamanın yolu.

Aşağıda, BufferLoader sınıfını nasıl kullanabileceğinize dair bir örnek verilmiştir. İki AudioBuffers oluşturalım ve yüklendikten sonra bunları aynı anda oynatalım.

window.onload = init;
var context;
var bufferLoader;

function init() {
    context = new AudioContext();

    bufferLoader = new BufferLoader(
    context,
    [
        '../sounds/hyper-reality/br-jam-loop.wav',
        '../sounds/hyper-reality/laughter.wav',
    ],
    finishedLoading
    );

    bufferLoader.load();
}

function finishedLoading(bufferList) {
    // Create two sources and play them both together.
    var source1 = context.createBufferSource();
    var source2 = context.createBufferSource();
    source1.buffer = bufferList[0];
    source2.buffer = bufferList[1];

    source1.connect(context.destination);
    source2.connect(context.destination);
    source1.noteOn(0);
    source2.noteOn(0);
}

Zamanla başa çıkma: ritmik sesler çalma

Web Audio API, geliştiricilerin oynatmayı hassas bir şekilde planlamasına olanak tanır. Bunu göstermek için basit bir ritim parçası oluşturalım. Muhtemelen en yaygın bilinen davul seti kalıbı şudur:

Basit bir rock davul ritmi

4/4'lük ölçüde, her sekizlik notada hi-hat'in, her dörtlük notada ise kick ve snare'in dönüşümlü olarak çalındığı ritim.

kick, snare ve hihat tamponlarını yüklediğimizi varsayarsak bunu yapacak kod basittir:

for (var bar = 0; bar < 2; bar++) {
    var time = startTime + bar * 8 * eighthNoteTime;
    // Play the bass (kick) drum on beats 1, 5
    playSound(kick, time);
    playSound(kick, time + 4 * eighthNoteTime);

    // Play the snare drum on beats 3, 7
    playSound(snare, time + 2 * eighthNoteTime);
    playSound(snare, time + 6 * eighthNoteTime);

    // Play the hi-hat every eighth note.
    for (var i = 0; i < 8; ++i) {
    playSound(hihat, time + i * eighthNoteTime);
    }
}

Burada, notalarda gördüğümüz sınırsız döngü yerine yalnızca bir tekrar yaparız. playSound işlevi, belirtilen bir zamanda arabellek oynatan bir yöntemdir. İşleyişi şu şekildedir:

function playSound(buffer, time) {
    var source = context.createBufferSource();
    source.buffer = buffer;
    source.connect(context.destination);
    source.noteOn(time);
}

Sesin düzeyini değiştirme

Bir sesle ilgili yapmak isteyebileceğiniz en temel işlemlerden biri ses seviyesini değiştirmektir. Web Audio API'yi kullanarak, ses seviyesini değiştirmek için kaynağımızı AudioGainNode üzerinden hedefine yönlendirebiliriz:

Sesin Kaynak'tan GainNode'a ve Hedef'e akışını gösteren basit akış şeması.

Bu bağlantı kurulumu aşağıdaki şekilde gerçekleştirilebilir:

// Create a gain node.
var gainNode = context.createGainNode();
// Connect the source to the gain node.
source.connect(gainNode);
// Connect the gain node to the destination.
gainNode.connect(context.destination);

Grafik ayarlandıktan sonra gainNode.gain.value öğesini aşağıdaki gibi değiştirerek ses seviyesini programatik olarak değiştirebilirsiniz:

// Reduce the volume.
gainNode.gain.value = 0.5;

İki ses arasında çapraz geçiş

Şimdi de biraz daha karmaşık bir senaryo olduğunu varsayalım. Bu senaryoda birden fazla ses çalıyoruz ancak bunlar arasında geçiş yapmak istiyoruz. Bu, DJ uygulamalarında sık karşılaşılan bir durumdur. Bu tür uygulamalarda iki pikap bulunur ve bir ses kaynağından diğerine geçiş yapmak istenir.

Bu işlem için aşağıdaki ses grafiği kullanılabilir:

Ayrı kazanç düğümlerine bağlı iki kaynağın aynı hedefe yönlendirildiği ses grafiği.

Bunu ayarlamak için iki AudioGainNodes oluşturup her kaynağı düğümler üzerinden bağlamamız yeterlidir. Örneğin, aşağıdaki gibi bir işlev kullanabiliriz:

function createSource(buffer) {
    var source = context.createBufferSource();
    // Create a gain node.
    var gainNode = context.createGainNode();
    source.buffer = buffer;
    // Turn on looping.
    source.loop = true;
    // Connect source to gain.
    source.connect(gainNode);
    // Connect gain to destination.
    gainNode.connect(context.destination);

    return {
    source: source,
    gainNode: gainNode
    };
}

Eşit güçte çapraz geçiş

Basit bir doğrusal çapraz geçiş yaklaşımı, örnekler arasında kaydırma yaptığınızda ses seviyesinde düşüşe neden olur.

Doğrusal geçiş, zaman içinde genlik seviyeleri olarak eşlenen iki tamamlayıcı düz çizgi şeklinde gösterilir.
Doğrusal geçiş

Bu sorunu çözmek için eşit güç eğrisi kullanırız. Bu eğride, karşılık gelen kazanç eğrileri doğrusal değildir ve daha yüksek bir genlikte kesişir. Bu sayede, ses bölgeleri arasındaki ses seviyesi düşüşleri en aza indirilir ve seviyeleri biraz farklı olabilecek bölgeler arasında daha dengeli bir geçiş sağlanır.

Eşit güçte çapraz geçiş, zaman içinde genlik seviyeleri olarak eşlenen iki eğri çizgiyle gösterilir.
Eşit güçte geçiş

Şarkı listelerinde çapraz geçiş

Diğer bir yaygın geçiş efekti uygulaması da müzik çalar uygulamalarıdır. Şarkı değiştiğinde, geçişin rahatsız edici olmaması için mevcut parçanın sesini azaltıp yeni parçanın sesini artırmak istiyoruz. Bunu yapmak için gelecekteki bir zamanda geçiş planlayın. Bu planlamayı yapmak için setTimeout kullanılabilir ancak bu kesin bir yöntem değildir. Web Audio API ile AudioGainNode'nin kazanç değeri gibi parametreler için gelecekteki değerleri planlamak üzere AudioParam arayüzünü kullanabiliriz.

Bu nedenle, bir oynatma listesi verildiğinde, şu anda çalınan parçanın sesini azaltıp bir sonraki parçanın sesini artırarak parçalar arasında geçiş yapabiliriz. Bu işlemler, mevcut parça çalmayı bitirmeden kısa bir süre önce yapılır:

function playHelper(bufferNow, bufferLater) {
    var playNow = createSource(bufferNow);
    var source = playNow.source;
    var gainNode = playNow.gainNode;
    var duration = bufferNow.duration;
    var currTime = context.currentTime;
    // Fade the playNow track in.
    gainNode.gain.linearRampToValueAtTime(0, currTime);
    gainNode.gain.linearRampToValueAtTime(1, currTime + ctx.FADE_TIME);
    // Play the playNow track.
    source.noteOn(0);
    // At the end of the track, fade it out.
    gainNode.gain.linearRampToValueAtTime(1, currTime + duration-ctx.FADE_TIME);
    gainNode.gain.linearRampToValueAtTime(0, currTime + duration);
    // Schedule a recursive track change with the tracks swapped.
    var recurse = arguments.callee;
    ctx.timer = setTimeout(function() {
    recurse(bufferLater, bufferNow);
    }, (duration - ctx.FADE_TIME) - 1000);
}

Web Audio API, RampToValue gibi bir parametrenin değerini kademeli olarak değiştirmek için kullanışlı bir linearRampToValueAtTime yöntemleri grubu sağlar.exponentialRampToValueAtTime

Geçiş zamanlama işlevi, yerleşik doğrusal ve üstel işlevler arasından seçilebilse de (yukarıdaki örnekte olduğu gibi) setValueCurveAtTime işlevini kullanarak bir değer dizisi aracılığıyla kendi değer eğrinizi de belirtebilirsiniz.

Sese basit bir filtre efekti uygulama

Sesin Kaynak'tan BiquadFilterNode'a ve Hedef'e akışını gösteren basit akış şeması.

Web Audio API, sesi bir ses düğümünden diğerine yönlendirmenize olanak tanır. Bu sayede, ses biçimlerinize karmaşık efektler eklemek için potansiyel olarak karmaşık bir işlemci zinciri oluşturabilirsiniz.

Bunu yapmanın bir yolu, ses kaynağınız ile hedefiniz arasına BiquadFilterNode'lar yerleştirmektir. Bu tür ses düğümleri, grafik ekolayzır ve daha karmaşık efektler oluşturmak için kullanılabilecek çeşitli düşük dereceli filtreler uygulayabilir. Bu filtreler, genellikle bir sesin frekans spektrumunun hangi kısımlarının vurgulanacağını ve hangilerinin bastırılacağını seçmekle ilgilidir.

Desteklenen filtre türleri şunlardır:

  • Alçak geçiren filtre
  • Yüksek geçiren filtre
  • Bant geçiren filtre
  • Alçak raf filtresi
  • Yüksek raf filtresi
  • Peaking filtresi
  • Çentik filtresi
  • Tüm geçiş filtresi

Tüm filtreler, belirli bir miktarda kazanç, filtreyi uygulama sıklığı ve kalite faktörünü belirten parametreler içerir. Alçak geçiren filtre, düşük frekans aralığını korur ancak yüksek frekansları atar. Kesme noktası, frekans değeriyle belirlenir. Q faktörü birimsizdir ve grafiğin şeklini belirler. Kazanç yalnızca alçak raf ve tepe filtreleri gibi belirli filtreleri etkiler, bu alçak geçiren filtreyi etkilemez.

Bir ses örneğinden yalnızca basları çıkarmak için basit bir alçak geçiren filtre oluşturalım:

// Create the filter
var filter = context.createBiquadFilter();
// Create the audio graph.
source.connect(filter);
filter.connect(context.destination);
// Create and specify parameters for the low-pass filter.
filter.type = 0; // Low-pass filter. See BiquadFilterNode docs
filter.frequency.value = 440; // Set cutoff to 440 HZ
// Playback the sound.
source.noteOn(0);

Genel olarak, frekans kontrollerinin logaritmik ölçekte çalışacak şekilde ayarlanması gerekir. Çünkü insan işitme duyusu da aynı prensiple çalışır (ör. A4 440 Hz, A5 880 Hz'dir). Daha fazla bilgi için yukarıdaki kaynak kodu bağlantısındaki FilterSample.changeFrequency işlevine bakın.

Son olarak, örnek kodun filtreyi bağlamanıza ve bağlantısını kaldırmanıza olanak tanıdığını, böylece AudioContext grafiğini dinamik olarak değiştirdiğini unutmayın. node.disconnect(outputNumber) işlevini çağırarak AudioNode'ların grafikle bağlantısını kesebiliriz. Örneğin, grafiği bir filtreden geçmek yerine doğrudan bağlantı üzerinden yönlendirmek için aşağıdaki işlemleri yapabiliriz:

// Disconnect the source and filter.
source.disconnect(0);
filter.disconnect(0);
// Connect the source directly.
source.connect(context.destination);

Daha fazla dinleme

Ses örneklerini yükleme ve oynatma da dahil olmak üzere API'nin temel özelliklerini ele aldık. Bazı yaygın ses efektlerini etkinleştirmek için kazanç düğümleri ve filtreler içeren ses grafikleri oluşturduk, sesleri ve ses parametrelerinde yapılan ince ayarları planladık. Bu noktada, harika web ses uygulamaları oluşturmaya hazırsınız.

İlham almak istiyorsanız birçok geliştirici Web Audio API'yi kullanarak harika çalışmalar oluşturdu. En sevdiklerimden bazıları:

  • AudioJedit: SoundCloud kalıcı bağlantılarını kullanan, tarayıcı içi bir ses birleştirme aracı.
  • Seslerin 3D bloklar üst üste yerleştirilerek oluşturulduğu bir ses sıralayıcı olan ToneCraft
  • Web Audio ve Web Sockets'i kullanan ortak müzik yapma oyunu Plink.