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 olsa da ses etiketi, gelişmiş oyunlar ve etkileşimli uygulamalar uygulamak için önemli sınırlamalar getirir.

Web Audio API, web uygulamalarında ses işleme ve sentezleme için kullanılan üst düzey bir JavaScript API'dir. Bu API'nin amacı, modern oyun ses motorlarında bulunan özellikleri ve modern masaüstü ses prodüksiyon uygulamalarında bulunan bazı miks, işleme ve filtreleme görevlerini dahil etmektir. Bu güçlü API'nin kullanımına dair kısa bir giriş yapacağız.

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 görev yapan herhangi bir sayıda ara AudioNodes'dan geçebilir. Bu yönlendirme, Web Audio spesifikasyonunda daha ayrıntılı olarak açıklanmaktadır.

Tek bir AudioContext ö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 olacak.

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

WebKit tabanlı eski tarayıcılar için webkitAudioContext ile olduğu gibi webkit ön ekini kullanın.

AudioNode'lar oluşturma ve ses dosyası verilerinin kodunu çözme gibi ilgi çekici Web Audio API işlevlerinin çoğu AudioContext yöntemleridir.

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; WAV, MP3, AAC, OGG ve diğerleri gibi birden fazla biçimde ses dosyası verilerinin yüklenmesini destekler. Farklı ses biçimleri için tarayıcı desteği değişiklik gösterir.

Aşağıdaki snippet'te bir ses örneğinin yüklenmesi gösterilmektedir:

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 makaleyi inceleyin.

(Kodu çözülmemiş) ses dosyası verileri alındıktan sonra, daha sonra kodu çözülmek üzere saklanabilir veya AudioContext decodeAudioData() yöntemi kullanılarak hemen kodu çözülebilir. Bu yöntem, request.response içinde depolanan ses dosyası verilerinin ArrayBuffer değerini alır ve eşzamansız olarak (ana JavaScript yürütme iş parçacığını engellemeden) kodunu çözer.

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

Ses çalınıyor

Bir veya daha fazla AudioBuffers yüklendikten sonra sesleri çalmaya hazır oluruz. Köpek havlaması sesini içeren bir AudioBuffer yüklediğimizi ve yüklemenin tamamlandığını varsayalım. Ardından bu arabelleği aşağıdaki kodla 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 kullanıcı bir tuşa bastığında veya fareyle bir şeyi tıkladığında çağrılabilir.

noteOn(time) işlevi, oyunlar ve zaman açısından kritik diğer uygulamalar için hassas ses oynatmayı planlamayı kolaylaştırır. Ancak bu zamanlamanın düzgün çalışması için ses arabelleklerinizin önceden yüklendiğinden emin olun.

Web Audio API'yi soyutlama

Elbette, bu belirli sesi yüklemek için sabit kodlu olmayan daha genel bir yükleme sistemi oluşturmak daha iyi olur. Bir ses uygulamasının veya oyunun kullanacağı çok sayıda kısa ve orta uzunluktaki sesle başa çıkmak için birçok yaklaşım vardır. Burada, BufferLoader (web standardının bir parçası değildir) kullanan bir yöntem gösterilmektedir.

Aşağıda, BufferLoader sınıfını nasıl kullanabileceğinize dair bir örnek verilmiştir. İki AudioBuffers oluşturalım ve yüklendikleri anda 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: Sesleri ritimle ç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 olarak bilinen davul kiti kalıbı şudur:

Basit bir rock davul ritmi

hi-hat her sekizlik notada, kick ve trampet ise her dörtlük notada dönüşümlü olarak çalınır.

kick, snare ve hihat arabelleklerini yüklediğimizi varsayarsak bunu yapmak için gereken 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 yapıyoruz. playSound işlevi, bir arabelleği belirtilen zamanda oynatan bir yöntemdir:

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

Ses seviyesini değiştirme

Bir sese uygulamak 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ı bir AudioGainNode aracılığıyla hedefine yönlendirebiliriz:

Sesin kaynaktan GainNode'a ve hedefe akışını gösteren basit bir akış şeması.

Bu bağlantı kurulumu aşağıdaki şekilde yapılabilir:

// 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 oluşturulduktan 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 geçiş yapma

Şimdi, birden fazla ses çaldığımız ancak bunlar arasında çapraz geçiş yapmak istediğimiz biraz daha karmaşık bir senaryomuz olduğunu varsayalım. Bu, iki pikabın bulunduğu ve bir ses kaynağından diğerine geçiş yapmak istediğimiz DJ benzeri bir uygulamada sıkça karşılaşılan bir durumdur.

Bu işlem, aşağıdaki ses grafiğiyle yapılabilir:

Ayrı kazanç düğümlerine bağlı iki kaynağı olan ve ardından aynı hedefe yönlendirilen ses grafiği.

Bunu ayarlamak için iki AudioGainNodes oluşturup her bir kaynağı düğümler üzerinden bağlarız. Bunun için aşağıdaki gibi bir işlev kullanırız:

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 geçiş yaparken ses seviyesinde düşüşe neden olur.

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

Bu sorunu çözmek için, karşılık gelen kazanç eğrilerinin doğrusal olmadığı ve daha yüksek bir genlikte kesiştiği eşit güç eğrisi kullanırız. Bu, ses bölgeleri arasındaki ses seviyesi düşüşlerini en aza indirerek seviyesi biraz farklı olabilecek bölgeler arasında daha eşit bir geçiş sağlar.

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 çapraz geçiş

Oynatma listesinde şarkılar arası geçiş

Başka bir yaygın çapraz geçiş uygulaması, müzik çalar uygulamasıdır. Şarkı değiştiğinde, geçişin rahatsız edici olmaması için mevcut parçanın sesini kısıp yeni parçanın sesini açmak istiyoruz. Bunun için ileri bir tarihte çapraz geçiş planlayın. Bu planlamayı yapmak için setTimeout kullanabiliriz ancak bu kesin bir yöntem değildir. Web Audio API ile AudioParam arayüzünü kullanarak AudioGainNode kazanç değeri gibi parametrelerin gelecekteki değerlerini planlayabiliriz.

Bu nedenle, bir oynatma listesi verildiğinde, o anda çalınan parçada kazanç azalması ve bir sonraki parçada kazanç artışı planlayarak parçalar arasında geçiş yapabiliriz. Her ikisi de mevcut parçanın çalınması bitmeden biraz önce gerçekleşir:

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, linearRampToValueAtTime ve exponentialRampToValueAtTime gibi bir parametrenin değerini kademeli olarak değiştirmek için kullanışlı bir RampToValue yöntemleri grubu sunar.

Geçiş zamanlama işlevi, yerleşik doğrusal ve üstel işlevler arasından seçilebilir (yukarıda gösterildiği gibi). Ayrıca, 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 kaynaktan BiquadFilterNode'a ve hedefe akışını gösteren basit bir akış şeması.

Web Audio API, bir ses düğümünden diğerine ses aktarmanıza olanak tanır. Bu sayede, ses dalgalarınıza karmaşık efektler eklemek için 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ırlar 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 bölümlerinin vurgulanacağını ve hangilerinin bastırılacağını seçmek için kullanılır.

Desteklenen filtre türleri şunlardır:

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

Tüm filtrelerde, belirli bir miktar kazanç, filtrenin uygulanacağı frekans ve kalite faktörünü belirten parametreler bulunur. Alçak geçiren filtre, düşük frekans aralığını korur ancak yüksek frekansları siler. Ayrılma 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ı ayıklamak 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 sistemi de aynı prensibe göre çalışır (ör. A4 440 Hz, A5 ise 880 Hz'dir). Daha fazla bilgi için yukarıdaki kaynak kodu bağlantısında FilterSample.changeFrequency işlevine bakın.

Son olarak, örnek kodun filtreyi bağlayıp bağlantısını kesmenize olanak tanıyarak AudioContext grafiğini dinamik olarak değiştirdiğini unutmayın. node.disconnect(outputNumber) yöntemini çağırarak AudioNode'ların grafikle bağlantısını kesebiliriz. Örneğin, grafiği bir filtreden geçmek yerine doğrudan bağlantıya yönlendirmek için aşağıdakileri 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 dahil olmak üzere API'nin temel özelliklerini ele aldık. Kazanç düğümleri ve filtreler içeren ses grafikleri oluşturduk ve bazı yaygın ses efektlerini etkinleştirmek için sesleri ve ses parametresi ayarlarını planladık. Artık harika web ses uygulamaları oluşturmaya hazırsınız.

İlham almak isterseniz birçok geliştirici Web Audio API'yi kullanarak harika çalışmalar yaptı. En sevdiklerimden bazıları şunlardır:

  • SoundCloud kalıcı bağlantılarını kullanan, tarayıcı içi ses birleştirme aracı AudioJedit.
  • ToneCraft, 3D blokları üst üste koyarak ses oluşturulan bir ses sıralayıcıdır.
  • Web Audio ve Web Sockets kullanan, ortak çalışmaya dayalı bir müzik yapma oyunu olan Plink.