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

Boris Smus
Boris Smus

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

Web Audio API, web uygulamalarında sesi 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 üretimi uygulamalarında bulunan bazı karıştırma, işleme ve filtreleme görevlerini kapsamaktır. Şimdi, bu güçlü API'yi kullanmaya nasıl başlayacağınıza bakalım.

AudioContext'i kullanmaya başlama

AudioContext, tüm sesleri yönetmek ve çalmak içindir. Web Audio API'sını 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ülü görevi gören çok sayıda ara AudioNodes üzerinden geçebilir. Bu yönlendirme, Web Sesi spesifikasyonunda daha ayrıntılı olarak açıklanmıştır.

Tek bir AudioContext örneği, birden çok ses girişini ve karmaşık ses grafiklerini destekleyebilir. Bu nedenle, oluşturduğumuz her ses uygulaması için bunlardan yalnızca bir tanesine 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');
    }
}

WebKit tabanlı daha eski tarayıcılarda, webkitAudioContext gibi webkit önekini kullanın.

AudioNodes oluşturma ve ses dosyası verilerinin kodunu çözme gibi birçok ilgi çekici Web Audio API işlevi, AudioContext yöntemidir.

Sesler yükleniyor

Web Audio API, kısa ve orta uzunlukta sesler için bir AudioBuffer kullanır. Temel yaklaşım, ses dosyalarını getirmek için XMLHttpRequest işlevini 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ş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 verilerdir (metin değil). Bu nedenle, isteğin responseType değerini 'arraybuffer' olarak ayarladık. ArrayBuffers hakkında daha fazla bilgi için XHR2 ile ilgili bu makaleyi inceleyin.

Kodu çözülen ses dosyası verileri alındıktan sonra, daha sonra kod çözme işlemi için bir yerde saklanabilir veya AudioContext decodeAudioData() yöntemi kullanılarak kodu hemen çözülebilir. Bu yöntem, request.response içinde depolanan ses dosyası verilerinin ArrayBuffer kadarını alır ve eşzamansız olarak bu verilerin kodunu çözer (ana JavaScript yürütme iş parçacığını engellemez).

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

Ses çalınıyor

Basit bir ses grafiği
Basit bir ses grafiği

Bir veya daha fazla AudioBuffers yüklendikten sonra ses çalmaya hazır hale geliriz. AudioBuffer adlı cihaza köpek havlaması sesi yüklediğimizi ve yüklemenin tamamlandığını varsayalım. Ardından bu tamponu 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, birisi fare ile bir tuşa bastığında veya bir öğeyi her tıkladığında çağrılabilir.

noteOn(time) işlevi, oyunlar ve zaman açısından kritik diğer uygulamalar için hassas ses oynatma planlamalarını 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'sını Soyutlama

Elbette, bu sesi yüklemek için kodu sabit bir şekilde yüklemeyen, 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 uzunlukta sesle başa çıkmak için birçok yaklaşım vardır. İşte BufferLoader kullanmanın bir yolu (web standardının parçası değildir).

Aşağıda, BufferLoader sınıfını nasıl kullanabileceğinize dair bir örnek verilmiştir. İki AudioBuffers oluşturalım ve yüklenir yüklenmez bunları da 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);
}

Zamana ayak uydurma: Sesleri ritimli çalma

Web Audio API, geliştiricilerin çalmayı hassas bir şekilde programlamasına olanak tanır. Bunu göstermek için basit bir ritim parçası oluşturalım. Muhtemelen en yaygın olarak bilinen drumkit kalıbı şöyledir:

Basit bir kaya davul deseni
Basit bir rock davul deseni

Sekizde bir notada bir hihat, 4/4'lük sürede ise her üç ayda bir sırayla vuruş ve trampet ç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ı yaparız. playSound işlevi, aşağıdaki gibi belirli bir zamanda bir tampon oynatan yöntemdir:

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

Sesin ses düzeyini değiştirme

Bir ses üzerinde yapmak isteyebileceğiniz en temel işlemlerden biri ses düzeyini değiştirmektir. Web Audio API'sını kullanarak, sesi değiştirmek için kaynağımızı bir AudioGainNode aracılığıyla hedefine yönlendirebiliriz:

Kazanç düğümlü ses grafiği
Kazanç düğümü olan ses grafiği

Bu bağlantı kurulumu şu ş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 ayarlandıktan sonra gainNode.gain.value değerini aşağıdaki gibi değiştirerek ses düzeyini programlı bir şekilde değiştirebilirsiniz:

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

İki sesin kesişmesi

Şimdi, birden fazla ses çaldığımız ancak bunların arasında geçiş yapmak istediğimiz biraz daha karmaşık bir senaryomuz olduğunu varsayalım. Bu durum, iki pikapa sahip olduğumuz ve bir ses kaynağından diğerine yatay kaydırma yapmak istediğimiz DJ benzeri uygulamalarda yaygın bir durumdur.

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

Kazanç düğümleri üzerinden bağlı iki kaynağı olan ses grafiği
Kazanç düğümleri aracılığıyla bağlanan iki kaynağı gösteren ses grafiği

Bunu ayarlamak için iki AudioGainNodes oluşturmamız ve aşağıdaki gibi bir işlev kullanarak her bir kaynağı düğümler üzerinden bağlamanız yeterlidir:

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

Naif doğrusal çapraz geçiş yaklaşımı, örnekler arasında gezinirken hacmin düştüğünü gösterir.

Doğrusal geçiş
Doğrusal geçiş

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

Eşit güç geçişi.
Eşit güç geçişi

Oynatma listesi çapraz geçişi

Diğer bir yaygın çapraz geçiş uygulaması, müzik çalar uygulaması içindir. Bir şarkı değiştiğinde, sarsıcı bir geçişin önüne geçmek için mevcut parçanın yerini yavaşça azaltmak ve yeni olanı yavaşça soluklamak istiyoruz. Bunu yapmak için, geleceğe geçiş planlayın. Bu planlamada setTimeout kullanılabilir ancak bu kesin değildir. Web Audio API ile AudioGainNode'nin kazanç değeri gibi parametrelerin gelecekteki değerlerini planlamak için AudioParam arayüzünü kullanabiliriz.

Böylece, oynatma listesi verildiğinde, o anda çalan parçada düşüş planlayarak parçalar arasında geçiş yapabiliriz. Aynı zamanda mevcut parçanın çalınması tamamlanmadan biraz önce bir sonraki parça için artış planlayabiliriz:

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 olanlardan (yukarıda gösterildiği gibi) seçilebilir ancak setValueCurveAtTime işlevini kullanarak bir değer dizisi aracılığıyla kendi değer eğrinizi de belirtebilirsiniz.

Seslere basit bir filtre efekti uygulama

BiquadFilterNode içeren ses grafiği
BiquadFilterNode ile ses grafiği

Web Audio API, sesi bir ses düğümünden diğerine aktarmanıza olanak tanıyarak ses formlarınıza karmaşık efektler eklemek için karmaşık olabilecek bir işlemci zinciri oluşturmanızı sağlar.

Bunu yapmanın bir yolu, ses kaynağınızla hedef arasına BiquadFilterNode yerleştirmektir. Bu tür ses düğümü, grafik ekolayzerler ve hatta daha karmaşık efektler oluşturmak için kullanılabilecek düşük düzeyli çeşitli filtreler yapabilir. Bu filtreler, çoğunlukla sesin frekans spektrumunun hangi kısımlarının vurgulanacağını ve hangilerinin azaltılacağını seçmeyle ilgilidir.

Desteklenen filtre türleri şunlardır:

  • Düşük geçiş filtresi
  • Yüksek geçiş filtresi
  • Bant kartı filtresi
  • Alt raf filtresi
  • Üst sınıf raf filtresi
  • Odak boyama filtresi
  • Çentik filtresi
  • Tüm kartlar filtresi

Tüm filtreler de bir miktar kazanç, filtrenin uygulanma sıklığı ve bir kalite faktörü belirtecek parametreler içerir. Alçak geçiş filtresi düşük frekans aralığını korur ancak yüksek frekansları siler. Kırılma noktası, frekans değeriyle belirlenir. Q faktörü de birimsizdir ve grafiğin şeklini belirler. Kazanç, bu düşük geçiren filtreyi değil, yalnızca düşük raf ve tepe noktası filtreleri gibi belirli filtreleri etkiler.

Bir ses örneğinden yalnızca bazları çıkarmak için basit bir düşük geçiş filtresi 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, insan işitmesi aynı ilkeye göre çalıştığından (yani, A4 440 Hz ve A5 880 Hz'dir) frekans kontrollerinin logaritmik ölçekte çalışması için ince ayar yapılması gerekir. Daha fazla bilgi için yukarıdaki kaynak kodu bağlantısında FilterSample.changeFrequency işlevine bakın.

Son olarak, örnek kodun, Audio Bağlam grafiğini dinamik olarak değiştirerek filtreyi bağlayıp bağlantısını kesebilmenizi sağladığını unutmayın. node.disconnect(outputNumber) yöntemini çağırarak AudioNodes'un grafikle bağlantısını kesebiliriz. Örneğin, grafiği bir filtreden doğrudan bağlantıya yeniden 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 çalma da dahil olmak üzere API'nin temel özelliklerini ele aldık. Kazanç düğümleri ve filtreler içeren sesli grafikler oluşturduk ve bazı yaygın ses efektlerini etkinleştirmek için planlanmış sesler ve ses parametresi ayarlamaları yaptık. Artık web'den güzel ses uygulamaları oluşturmaya hazırsınız!

İlham arıyorsanız birçok geliştirici Web Audio API'sını kullanarak zaten harika işler ortaya koymuştur. En beğendiklerim şunlardan bazıları:

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