Primeiros passos com a API Web Audio

Antes do elemento HTML5 <audio>, era necessário Flash ou outro plug-in quebrar o silêncio da Web. O áudio na Web não está mais disponível exige um plug-in, a tag de áudio traz limitações a implementação de jogos sofisticados e aplicativos interativos.

A API Web Audio é uma API JavaScript de alto nível para processamento e sintetizar áudio em aplicativos da Web. O objetivo dessa API é incluem recursos de mecanismos de áudio de jogos modernos e alguns tarefas de combinação, processamento e filtragem que são encontradas nos computadores modernos aplicativos de produção de áudio. A seguir, há uma introdução leve usando essa API poderosa.

Primeiros passos com o AudioContext

O AudioContext serve para gerenciar e reproduzir todos os sons. Para produzir um som usando a API de áudio da Web, criar uma ou mais fontes de som e as conecta ao destino do som fornecido pelo AudioContext. instância. Essa conexão não precisa ser direta e pode passar qualquer número de AudioNodes intermediários que atuam como para o sinal de áudio. Esse roteamento é descrito na detalhes na especificação de áudio da Web.

Uma única instância de AudioContext pode oferecer suporte a várias entradas de som e gráficos de áudio complexos, então só vamos precisar de um deles para cada aplicativo de áudio que criamos.

O snippet a seguir cria um AudioContext:

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

Para navegadores mais antigos baseados no WebKit, use o prefixo webkit, assim como webkitAudioContext.

Muitas das funcionalidades interessantes da API de áudio da web, como a criação Os AudioNodes e a decodificação de dados de arquivos de áudio são métodos de AudioContext.

Carregando sons

A API de áudio da web usa um AudioBuffer para faixas de curta e média sons A abordagem básica é usar XMLHttpRequest para buscar arquivos de som.

A API oferece suporte ao carregamento de dados de arquivos de áudio em vários formatos, como WAV, MP3, AAC, OGG e outros. O navegador oferece suporte a diferentes formatos de áudio variam.

O snippet a seguir demonstra o carregamento de uma amostra de som:

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

Os dados do arquivo de áudio são binários (não texto). Por isso, definimos o responseType. da solicitação para 'arraybuffer'. Para mais informações sobre ArrayBuffers, consulte este artigo sobre XHR2.

Uma vez recebidos os dados do arquivo de áudio (não decodificados), eles podem ser mantidos para decodificação posterior ou pode ser decodificado imediatamente usando o Método decodeAudioData() do AudioContext. Esse método recebe ArrayBuffer de dados de arquivos de áudio armazenados nos request.response e o decodifica de modo assíncrono (sem bloquear a execução principal do JavaScript) fio).

Quando decodeAudioData() é concluído, ele chama uma função de callback que fornece os dados de áudio PCM decodificados como um AudioBuffer.

Como reproduzir sons

Um gráfico de áudio simples
Um gráfico de áudio simples

Quando um ou mais AudioBuffers forem carregados, a reprodução estará pronta sons Vamos supor que acabamos de carregar uma AudioBuffer com o som de um cachorro latindo e que o carregamento foi concluído. Então, podemos jogar esse buffer com o código a seguir.

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
}

A função playSound() pode ser chamada sempre que alguém pressionar uma tecla ou clica em algo com o mouse.

A função noteOn(time) facilita a programação de sons precisos a reprodução de jogos e outros aplicativos urgentes. No entanto, para ter que esta programação funcione corretamente, garanta que os buffers de som estejam pré-carregado.

Como abstrair a API de áudio da Web

Obviamente, o melhor seria criar um sistema de carregamento mais geral que não está codificado para carregar esse som específico. Existem várias abordagens para lidar com os muitos sons de comprimento curto a médio que que um aplicativo ou jogo de áudio usaria. Veja aqui uma maneira de usar um BufferLoader (não faz parte do padrão da Web).

Confira abaixo um exemplo de como usar a classe BufferLoader. Vamos criar dois AudioBuffers. e, assim que são carregados, vamos tocá-los ao mesmo tempo.

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

Como lidar com o tempo: tocando sons com ritmo

A API de áudio da Web permite que os desenvolvedores programem com precisão a reprodução. Para demonstrar isso, vamos configurar uma faixa de ritmo simples. Provavelmente o padrão de bateria mais conhecido é o seguinte:

Um padrão simples de bateria de rock
Um padrão simples de bateria de rock

em que um hihat é tocado a cada colcheia, e chute e caixa são tocava alternadamente a cada trimestre, em 4/4 no tempo.

Supondo que os buffers kick, snare e hihat tenham sido carregados, o para fazer isso é simples:

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

Aqui, fazemos apenas uma repetição, em vez do loop ilimitado que vemos em partitura. A função playSound é um método que desempenha uma buffer em um horário especificado, da seguinte maneira:

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

Como mudar o volume de um som

Uma das operações mais básicas que se pode fazer com um som é alterar o volume. Com a API de áudio da Web, podemos encaminhar nossa origem para seu destino por meio de um AudioGainNode para manipular volume:

Gráfico de áudio com um nó de ganho
Gráfico de áudio com um nó de ganho

Essa configuração de conexão pode ser feita da seguinte maneira:

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

Após a configuração do gráfico, é possível alterar programaticamente as volume manipulando o gainNode.gain.value da seguinte maneira:

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

Esmaecimento cruzado entre dois sons

Agora, suponha que temos um cenário um pouco mais complexo, em que estamos reproduzindo vários sons, mas deseja fazer o esmaecimento cruzado entre eles. Esta é uma um caso comum em um aplicativo para DJs, em que temos dois toca-discos e querem movimentar de uma fonte de som para outra.

Isso pode ser feito com o seguinte gráfico de áudio:

Gráfico de áudio com duas origens conectadas pelos nós de ganho
Gráfico de áudio com duas origens conectadas pelos nós de ganho

Para configurar isso, basta criar dois AudioGainNodes e conectar cada origem pelos nós, usando algo parecido com esta função:

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

Crossfading de mesma potência

Uma abordagem de crossfade linear simples mostra uma queda de volume conforme você se movimenta entre as amostras.

Um cross-fading linear
Um crossfade linear

Para resolver esse problema, usamos uma curva de igual potência, na qual o curvas de ganho correspondentes não são lineares e se cruzam a um amplitude. Isso minimiza as quedas de volume entre as regiões de áudio, resultando em um crossfade mais uniforme entre regiões que podem ser de diferentes níveis.

Um cross-fading de mesma potência.
Um crossfade de mesma potência

Crossfading de playlists

Outro aplicativo comum de crossfader é um aplicativo de player de música. Quando uma música muda, queremos esmaecer a faixa atual e esmaecer a faixa para evitar uma transição desagradável. Para fazer isso, agende uma o crossfade, para o futuro. Poderíamos usar setTimeout para fazer isso, programação, isso não é preciso. Com a API de áudio da Web, pode usar a interface AudioParam para programar valores futuros para parâmetros, como o valor de ganho de um AudioGainNode.

Assim, com base em uma lista de reprodução, podemos fazer a transição entre as faixas agendando uma redução de ganho na faixa em reprodução e um aumento de ganho na a próxima, ambas um pouco antes da faixa atual terminar:

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

A API Web Audio oferece um conjunto conveniente de métodos RampToValue para alterar gradualmente o valor de um parâmetro, como linearRampToValueAtTime e exponentialRampToValueAtTime.

Embora a função de tempo de transição possa ser escolhida entre os dados lineares integrados e exponenciais (como acima), também é possível especificar seu próprio valor curva usando uma matriz de valores usando a função setValueCurveAtTime.

Como aplicar um efeito de filtro simples a um som

Gráfico de áudio com um BiquadFilterNode
Um gráfico de áudio com um BiquadFilterNode

A API de áudio da Web permite que você canalize o som de um nó de áudio para outro, criando uma cadeia potencialmente complexa de processadores para adicionar às formas sonoras.

Uma maneira de fazer isso é colocar BiquadFilterNodes entre o som origem e destino. Esse tipo de nó de áudio pode fazer várias filtros de baixa ordem que podem ser usados para construir equalizadores gráficos e até mesmo efeitos mais complexos, principalmente para selecionar quais partes do espectro de frequência de um som a enfatizar e refutar.

Os tipos de filtro aceitos incluem:

  • Filtro de passagem de baixas frequências
  • Filtro de passagem de altas frequências
  • Filtro de passagem de banda
  • Filtro de prateleira baixa
  • Filtro de nível alto
  • Filtro de pico
  • Filtro de entalhe
  • Filtro de passagem total

E todos os filtros incluem parâmetros para especificar uma quantidade gain, a frequência de aplicação do filtro e um fator de qualidade. O filtro de passagem de baixas frequências mantém a faixa de frequência mais baixa, mas descarta as altas e frequências diferentes. O ponto de interrupção é determinado pelo valor da frequência, e o fator Q não tem unidade e determina a forma da gráfico. O ganho afeta apenas certos filtros, como os filtros de prateleira filtros de pico, e não esse filtro passa-baixo.

Vamos configurar um filtro de passagem de baixas frequências simples para extrair apenas as bases de um amostra de som:

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

Em geral, os controles de frequência precisam ser ajustados para funcionar na escala logarítmica, já que a audição humana segue o mesmo princípio (ou seja, A4 é 440 Hz e A5 é 880 Hz). Para mais detalhes, consulte a FilterSample.changeFrequency no link de código-fonte acima.

Por fim, observe que o exemplo de código permite conectar e desconectar a filtro, alterando dinamicamente o gráfico do AudioContext. Podemos nos desconectar AudioNodes do gráfico chamando node.disconnect(outputNumber). Por exemplo, para redirecionar o gráfico de um filtro para um conexão direta, podemos fazer o seguinte:

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

Ouça mais

Abordamos os conceitos básicos da API, incluindo como carregar e reproduzir áudio de amostra. Criamos gráficos de áudio com nós e filtros de ganho Ajustes de sons e parâmetros de áudio programados para ativar alguns sons comuns efeitos Agora, você já pode criar uma teia doce aplicativos de áudio.

Se você está em busca de inspiração, muitos desenvolvedores já criaram ótimo trabalho usando a API Web Audio. Alguns dos meus favoritos incluem:

  • AudioJedit, uma ferramenta de combinação de som no navegador que usa Links permanentes do SoundCloud.
  • ToneCraft, um sequenciador em que os sons são criados empilhando blocos 3D.
  • Plink, um jogo colaborativo de produção musical usando áudio e Web da Web Soquetes.