Antes do elemento <audio>
do HTML5, o Flash ou outro plug-in era necessário
para quebrar o silêncio da Web. Embora o áudio na Web não precise mais
de um plug-in, a tag de áudio traz limitações significativas para
implementar jogos sofisticados e aplicativos interativos.
A API Web Audio é uma API JavaScript de alto nível para processamento e sintetização de áudio em aplicativos da Web. O objetivo dessa API é incluir recursos encontrados em mecanismos de áudio de jogos modernos e algumas das tarefas de mixagem, processamento e filtragem encontradas em aplicativos de produção de áudio para computadores modernos. A seguir, uma introdução simples sobre como usar essa API poderosa.
Introdução ao AudioContext
Um AudioContext serve para gerenciar e tocar todos os sons. Para produzir
um som usando a API Web Audio, crie uma ou mais fontes de som
e conecte-as ao destino de som fornecido pela instância
AudioContext
. Essa conexão não precisa ser direta e pode passar por
qualquer número de AudioNodes intermediários que atuam como módulos de processamento
do sinal de áudio. Esse roteamento é descrito com mais detalhes
na especificação do Web Audio.
Uma única instância de AudioContext
pode oferecer suporte a várias entradas de som
e gráficos de áudio complexos. Portanto, só vamos precisar de uma delas para cada
aplicação de áudio criada.
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 em WebKit, use o prefixo webkit
, como
webkitAudioContext
.
Muitas das funcionalidades interessantes da API Web Audio, como a criação de
AudioNodes e a decodificação de dados de arquivos de áudio, são métodos de AudioContext
.
Sons de carregamento
A API Web Audio usa um AudioBuffer para sons curtos a médios. A abordagem básica é usar o 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 suporte do navegador para diferentes formatos de áudio varia.
O snippet abaixo demonstra o carregamento de um exemplo 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), então definimos o responseType
da solicitação como 'arraybuffer'
. Para mais informações sobre
ArrayBuffers
, consulte este artigo sobre XHR2.
Depois que os dados do arquivo de áudio (não decodificados) forem recebidos, eles poderão ser mantidos
para decodificação posterior ou decodificados imediatamente usando o
método decodeAudioData()
do AudioContext. Esse método usa o
ArrayBuffer
dos dados do arquivo de áudio armazenados em request.response
e
o decodifica de forma assíncrona (sem bloquear a linha de execução
principal de execução do JavaScript).
Quando decodeAudioData()
é concluído, ele chama uma função de callback que
fornece os dados de áudio PCM decodificados como um AudioBuffer
.
Tocar sons
Quando um ou mais AudioBuffers
são carregados, podemos reproduzir
sons. Vamos supor que acabamos de carregar um AudioBuffer
com o som
de um cachorro latindo e que o carregamento foi concluído. Em seguida, podemos reproduzir
esse buffer com o seguinte código.
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
}
Essa função playSound()
pode ser chamada sempre que alguém pressiona uma tecla ou
clica em algo com o mouse.
A função noteOn(time)
facilita a programação de reprodução de som
precisa para jogos e outros aplicativos de tempo crítico. No entanto, para que
essa programação funcione corretamente, verifique se os buffers de som estão
pré-carregados.
Como abstrair a API Web Audio
Claro, seria melhor criar um sistema de carregamento mais geral que não seja fixado para carregar esse som específico. Há muitas abordagens para lidar com os muitos sons de duração curta a média que um aplicativo de áudio ou jogo usaria. Aqui está uma maneira de usar um BufferLoader (não faz parte do padrão da Web).
Confira a seguir um exemplo de como usar a classe BufferLoader
.
Vamos criar dois AudioBuffers
e, assim que eles forem carregados,
vamos reproduzir 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);
}
Lidar com o tempo: tocar sons com ritmo
A API Web Audio permite que os desenvolvedores programem a reprodução com precisão. Para demonstrar isso, vamos configurar uma faixa de ritmo simples. Provavelmente, o padrão de drumkit mais conhecido é o seguinte:
em que um hi-hat é tocado a cada colcheia, e o bumbo e a caixa são tocados alternadamente a cada quarto, em tempo 4/4.
Supondo que tenhamos carregado os buffers kick
, snare
e hihat
, o
código 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 na
partitura. A função playSound
é um método que reproduz um
buffer em um momento especificado, conforme mostrado abaixo:
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 você pode fazer com um som é mudar o volume. Usando a API Web Audio, podemos encaminhar nossa origem para o destino usando um AudioGainNode para manipular o volume:
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);
Depois que o gráfico for configurado, você poderá mudar o volume de maneira programática, manipulando o gainNode.gain.value
da seguinte maneira:
// Reduce the volume.
gainNode.gain.value = 0.5;
Transição entre dois sons
Agora, suponha que temos um cenário um pouco mais complexo, em que tocamos vários sons, mas queremos fazer um crossfade entre eles. Esse é um caso comum em um aplicativo semelhante a um DJ, em que temos dois toca-discos e queremos poder fazer a panorâmica de uma fonte de som para outra.
Isso pode ser feito com o seguinte gráfico de áudio:
Para configurar isso, basta criar dois AudioGainNodes e conectar cada fonte pelos nós, usando algo como 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
};
}
Equalização de potência
Uma abordagem de transição linear simples mostra uma queda de volume à medida que você alterna entre as amostras.
Para resolver esse problema, usamos uma curva de potência igual, em que as curvas de ganho correspondentes não são lineares e se cruzam em uma amplitude maior. Isso minimiza as quedas de volume entre as regiões de áudio, resultando em um crossfade mais uniforme entre regiões que podem ser ligeiramente diferentes no nível.
Transição entre músicas em playlists
Outro aplicativo comum de crossfader é para um app de player de música.
Quando uma música muda, queremos diminuir o volume da faixa atual e aumentar o
volume da nova para evitar uma transição brusca. Para fazer isso, programe uma
transição suave para o futuro. Embora possamos usar setTimeout
para fazer essa
programação, ela não é precisa. Com a API Web Audio, podemos
usar a interface AudioParam para programar valores futuros para
parâmetros, como o valor de ganho de um AudioGainNode
.
Assim, com uma playlist, podemos fazer a transição entre as faixas programando uma redução de ganho na faixa que está sendo reproduzida e um aumento de ganho na próxima, ambos um pouco antes de a faixa atual terminar de ser reproduzida:
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
mudar gradualmente o valor de um parâmetro, como
linearRampToValueAtTime
e exponentialRampToValueAtTime
.
Embora a função de tempo de transição possa ser escolhida entre linear
e exponencial integradas (como acima), você também pode especificar sua própria curva
de valor usando uma matriz de valores com a função setValueCurveAtTime
.
Aplicação de um efeito de filtro simples a um som
A API Web Audio permite transmitir som de um nó de áudio para outro, criando uma cadeia potencialmente complexa de processadores para adicionar efeitos complexos às suas formas de som.
Uma maneira de fazer isso é colocar BiquadFilterNodes entre a fonte e o destino do som. Esse tipo de nó de áudio pode fazer vários filtros de ordem baixa, que podem ser usados para criar equalizadores gráficos e até efeitos mais complexos, principalmente para selecionar quais partes do espectro de frequência de um som devem ser enfatizadas e quais devem ser atenuadas.
Os tipos de filtros compatíveis incluem:
- Filtro passa-baixa
- Filtro passa-alta
- Filtro passa-banda
- Filtro de prateleira baixa
- Filtro de prateleira alta
- Filtro de pico
- Filtro de entalhe
- Filtro "aprovar tudo"
Todos os filtros incluem parâmetros para especificar uma quantidade de ganho, a frequência de aplicação do filtro e um fator de qualidade. O filtro passa-baixa mantém o intervalo de frequência mais baixa, mas descarta as frequências altas. O ponto de interrupção é determinado pelo valor da frequência, e o fator Q não tem unidades e determina a forma do gráfico. O ganho afeta apenas alguns filtros, como os filtros de prateleira baixa e de pico, e não este filtro passa-baixa.
Vamos configurar um filtro de passagem baixa simples para extrair apenas as bases de uma 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 em uma
escala logarítmica, já que a audição humana funciona com o mesmo princípio
(ou seja, A4 é 440 Hz e A5 é 880 Hz). Para mais detalhes, consulte a
função FilterSample.changeFrequency
no link do código-fonte acima.
Por fim, o código de exemplo permite conectar e desconectar o
filtro, mudando dinamicamente o gráfico AudioContext. Podemos desconectar
AudioNodes do gráfico chamando node.disconnect(outputNumber)
.
Por exemplo, para redirecionar o gráfico de um filtro para uma
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);
Mais músicas
Abordamos os conceitos básicos da API, incluindo o carregamento e a reprodução de amostras de áudio. Criamos gráficos de áudio com nós e filtros de ganho, além de sons programados e ajustes de parâmetros de áudio para ativar alguns efeitos sonoros comuns. Agora você está pronto para criar alguns aplicativos de áudio da Web.
Se você está em busca de inspiração, muitos desenvolvedores já criaram ótimos trabalhos usando a API Web Audio. Alguns dos meus favoritos incluem:
- AudioJedit, uma ferramenta de junção de áudio no navegador que usa os links permanentes do SoundCloud.
- ToneCraft, um sequenciador de som em que os sons são criados empilhando blocos 3D.
- Plink, um jogo colaborativo de criação de música que usa o Web Audio e o Web Sockets.