Antes do elemento HTML5 <audio>
, 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 a implementação de jogos sofisticados e aplicativos interativos.
A API Web Audio é uma API JavaScript de alto nível usada para processar e sintetizar áudio em aplicativos da Web. O objetivo dessa API é incluir recursos encontrados em mecanismos modernos de áudio de jogos e algumas das tarefas de mistura, processamento e filtragem encontradas em aplicativos modernos de produção de áudio para computadores. Confira abaixo uma introdução simples ao uso dessa API avançada.
Introdução ao AudioContext
O AudioContext serve para gerenciar e reproduzir todos os sons. Para produzir
um som usando a API Web Audio, crie uma ou mais fontes de som
e conecte-as ao destino 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, precisaremos apenas de uma delas para cada
aplicativo de áudio que criarmos.
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
, 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
.
Carregando sons
A API Web Audio usa um AudioBuffer para sons de curto a médio duração. 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 suporte do navegador para diferentes formatos de áudio varia.
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();
}
Como os dados do arquivo de áudio são binários (não texto), 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) são recebidos, eles podem ser mantidos
para decodificação posterior ou podem ser decodificados imediatamente usando o
método decodeAudioData()
do AudioContext. Esse método usa o
ArrayBuffer
de dados do arquivo de áudio armazenados em request.response
e
o decodifica de maneira assíncrona, sem bloquear a linha de execução principal
do JavaScript.
Quando decodeAudioData()
é concluído, ele chama uma função de callback que
fornece os dados de áudio PCM decodificados como um AudioBuffer
.
Reproduzindo sons
Depois que um ou mais AudioBuffers
forem carregados, estará tudo pronto para tocar
sons. Vamos supor que acabamos de carregar uma AudioBuffer
com o som
de um cachorro latindo e que o carregamento foi concluído. Depois, podemos reproduzir 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
}
Essa função playSound()
pode ser chamada toda vez que alguém pressionar uma tecla ou clicar em algo com o mouse.
A função noteOn(time)
facilita a programação de reproduções de som precisas para jogos e outros aplicativos urgentes. No entanto, para que
essa programação funcione corretamente, verifique se os buffers de som estão
pré-carregados.
Como abstrair a API de áudio da Web
Obviamente, seria melhor criar um sistema de carregamento mais geral que não seja codificado para carregar esse som específico. Há muitas abordagens para lidar com os vários sons de comprimento curto a médio que um app ou jogo de áudio usaria. Confira uma maneira de usar um BufferLoader, que 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 eles forem carregados,
vamos reproduzi-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: como tocar sons com ritmo
A API de áudio da web permite que os desenvolvedores programem a reprodução de forma precisa. Para demonstrar isso, vamos configurar uma faixa de ritmo simples. Provavelmente, o padrão de bateria mais conhecido é este:
em que um hihat é tocado a cada colcheia, e o chute e a caixa são tocados de forma alternada a cada quarto, em 4/4.
Supondo que os buffers kick
, snare
e hihat
foram carregados, 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 horário especificado, desta forma:
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 a um som é mudar o volume dele. Com a API Web Audio, podemos rotear nossa origem ao destino por meio de 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);
Após a configuração do gráfico, é possível alterar o volume de forma programática
manipulando o gainNode.gain.value
da seguinte maneira:
// Reduce the volume.
gainNode.gain.value = 0.5;
Cross-fading entre dois sons
Agora, suponha que temos um cenário um pouco mais complexo, em que estamos reprotando vários sons, mas queremos fazer a transição entre eles. Esse é um caso comum em um aplicativo para DJs, em que temos dois toca-discos e queremos fazer a movimentação 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 origem por meio dos nós, usando algo semelhante a 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 linear simples de fading cruzado exibe uma queda de volume à medida que você movimenta as amostras.
Para resolver esse problema, usamos uma curva de igual potência, 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 cross-fading mais uniforme entre as regiões que podem ter níveis um pouco diferentes.
Crossfading de playlist
Outro aplicativo comum que usa crossfader é para aplicativos de reprodução de música.
Quando uma música muda, queremos esmaecer a faixa atual e mostrar a
nova para evitar uma transição desagradável. Para fazer isso, programe um crossfade para o futuro. Embora possamos usar setTimeout
para fazer essa
programação, isso não é preciso. 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 em reprodução e um aumento de ganho na próxima faixa, ambos um pouco antes de a 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 fornece 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 as lineares e exponenciais integradas (como acima), você também pode especificar sua própria curva de valor por meio de uma matriz de valores usando a função setValueCurveAtTime
.
Como aplicar um efeito de filtro simples a um som
A API Web Audio permite canalizar o som de um nó de áudio para outro, criando uma cadeia de processadores potencialmente complexa para adicionar efeitos complexos às formas sonoras.
Uma maneira de fazer isso é colocar BiquadFilterNodes entre a origem e o destino do som. Esse tipo de nó de áudio pode realizar 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 serão enfatizadas e quais diminuir.
Os tipos de filtros compatíveis incluem:
- Filtro de passagem de baixas frequências
- Filtro de passagem de altas frequências
- Filtro de passagem de banda
- Filtro de nível baixo
- Filtro de nível alto
- Filtro de pico
- Filtro de entalhe
- Filtro de todos os cartões
Além disso, todos os filtros incluem parâmetros para especificar uma determinada quantidade de ganho, a frequência com que o filtro será aplicado e um fator de qualidade. O filtro de passagem baixa mantém o intervalo de frequência mais baixo, mas descarta as frequências altas. O ponto de interrupção é determinado pelo valor da frequência, o fator Q não tem unidades e determina a forma do gráfico. O ganho afeta apenas determinados filtros, como os de nível inferior e de pico, e não o filtro de passagem de baixas frequências.
Vamos configurar um filtro de passagem de baixas frequências 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 própria audição humana funciona com base no mesmo princípio
(ou seja, A4 é de 440 hz e A5 é 880 Hz). Para mais detalhes, consulte a função FilterSample.changeFrequency
no link do código-fonte acima.
Por fim, observe que o exemplo de código permite conectar e desconectar o
filtro, mudando dinamicamente o gráfico do AudioContext. É possível desconectar
AudioNodes do gráfico chamando node.disconnect(outputNumber)
.
Por exemplo, para redirecionar o gráfico de um filtro a uma conexão direta, faça o seguinte:
// Disconnect the source and filter.
source.disconnect(0);
filter.disconnect(0);
// Connect the source directly.
source.connect(context.destination);
Como ouvir mais
Abordamos os conceitos básicos da API, incluindo como carregar e reproduzir amostras de áudio. Criamos gráficos de áudio com nós e filtros de ganho e programamos sons e ajustes de parâmetros de áudio para permitir alguns efeitos sonoros comuns. Agora, você está pronto para criar alguns aplicativos incríveis de áudio da Web.
Se você está em busca de inspiração, muitos desenvolvedores já criaram um ótimo trabalho usando a API Web Audio. Alguns dos meus favoritos incluem:
- AudioJedit, uma ferramenta de fusão de som no navegador que usa links permanentes do SoundCloud.
- ToneCraft, um sequenciador de som em que os sons são criados ao empilhar blocos 3D.
- Plink, um jogo colaborativo de criação musical que usa Web Audio e Web Sockets.