API de áudio da Web: por que compor quando você pode codificar?

Publicados: 2022-03-11

O primeiro rascunho de uma API de áudio da Web apareceu no W3C em 2011. Embora o áudio em páginas da Web tenha sido suportado por um longo tempo, uma maneira adequada de gerar áudio a partir do navegador da Web não estava disponível até recentemente. Pessoalmente, atribuo isso ao Google Chrome, porque quanto ao interesse do Google, o navegador começou a se tornar a parte mais importante de um computador. Você deve se lembrar, o reino do navegador da web não começou a mudar muito até que o Google Chrome apareceu. Se você usasse som em uma página da Web nessa época, teria sido uma má decisão de design. Mas desde que surgiu a ideia de experimentos na web, o áudio da web começou a fazer sentido novamente. Os navegadores da Web hoje em dia são outra ferramenta de expressão artística, e o vídeo e o áudio no navegador da Web desempenham um papel vital nisso.

API de áudio da Web: por que compor quando você pode codificar?

API de áudio da Web: por que compor quando você pode codificar?
Tweet

A API de áudio da Web pode ser bastante difícil de usar para alguns propósitos, pois ainda está em desenvolvimento, mas já existem várias bibliotecas JavaScript para facilitar as coisas. Neste caso, mostrarei como começar a usar a API de áudio da Web usando uma biblioteca chamada Tone.js. Com isso, você poderá cobrir a maioria das necessidades de som do seu navegador apenas aprendendo o básico.

Olá API de áudio da Web

Começando

Começaremos sem usar a biblioteca. Nosso primeiro experimento envolverá fazer três ondas senoidais. Como este será um exemplo simples, criaremos apenas um arquivo chamado hello.html, um arquivo HTML simples com uma pequena quantidade de marcação.

 <!DOCTYPE html> <html> <head> <meta charset="utf‐8"> <title> Hello web audio </title> </head> <body> </body> <script> </script> </html>

O núcleo da API de áudio da Web é o contexto de áudio. O contexto de áudio é um objeto que conterá tudo relacionado ao áudio da web. Não é considerado uma boa prática ter mais de um contexto de áudio em um único projeto. Começaremos instanciando um contexto de áudio seguindo as recomendações fornecidas pela documentação da API de áudio da Web da Mozilla.

 var audioCtx = new (window.AudioContext || window.webkitAudioContext);

Fazendo um oscilador

Com um contexto de áudio instanciado, você já tem um componente de áudio: o audioCtx.destination. Isto é como o seu alto-falante. Para fazer um som, você deve conectá-lo ao audioCtx.destination. Agora para produzir algum som, vamos criar um oscilador:

 var sine = audioCtx.createOscillator();

Ótimo, mas não o suficiente. Ele também precisa ser iniciado e conectado ao nosso audioCtx.destination:

 sine.start(); sine.connect(audioCtx.destination);

Com essas quatro linhas, você terá uma página da Web bastante irritante que reproduz um som senoidal, mas agora você entende como os módulos podem se conectar uns aos outros. No script a seguir, haverá três tons em forma de seno, conectados à saída, cada um com um tom diferente. O código é bem autoexplicativo:

 //create the context for the web audio var audioCtx = new (window.AudioContext || window.webkitAudioContext)(); //create, tune, start and connect each oscillator sinea, sineb and sinec var sinea = audioCtx.createOscillator(); sinea.frequency.value = 440; sinea.type = "sine"; sinea.start(); sinea.connect(audioCtx.destination); var sineb = audioCtx.createOscillator(); sineb.frequency.value = 523.25; sineb.type = "sine"; sineb.start(); sineb.connect(audioCtx.destination); var sinec = audioCtx.createOscillator(); sinec.frequency.value = 698.46; sinec.type = "sine"; sinec.start(); sinec.connect(audioCtx.destination);

Os osciladores não se restringem a ondas senoidais, mas também podem ser triangulares, dente de serra, quadrados e de formato personalizado, conforme indicado no MDN.

Lógica de patch de áudio da Web

Em seguida, adicionaremos um módulo de ganho à nossa orquestra de componentes de áudio da Web. Este módulo permite-nos alterar a amplitude dos nossos sons. É semelhante a um botão de volume. Já usamos a função connect para conectar um oscilador à saída de áudio. Podemos usar a mesma função de conexão para conectar qualquer componente de áudio. Se você estiver usando o Firefox e der uma olhada no console de áudio da web, verá o seguinte:

Se quisermos alterar o volume, nosso patch deve ficar assim:

O que significa que os osciladores não estão mais conectados ao destino de áudio, mas sim a um módulo de ganho, e esse módulo de ganho está conectado ao destino. É bom sempre imaginar que você faz isso com pedais e cabos de guitarra. O código ficará assim:

 var audioCtx = new (window.AudioContext || window.webkitAudioContext) // we create the gain module, named as volume, and connect it to our var volume = audioCtx.createGain(); volume.connect(audioCtx.destination); //these sines are the same, exept for the last connect statement. //Now they are connected to the volume gain module and not to the au var sinea = audioCtx.createOscillator(); sinea.frequency.value = 440; sinea.type = "sine"; sinea.start(); sinea.connect(volume); var sineb = audioCtx.createOscillator(); sineb.frequency.value = 523.25; sineb.type = "sine"; sineb.start(); sineb.connect(volume); var sinec = audioCtx.createOscillator(); sinec.frequency.value = 698.46; sinec.type = "sine"; sinec.start(); sinec.connect(volume); volume.gain.value=0.2;

Você pode encontrar a solução em https://github.com/autotel/simple-webaudioapi/.

GainNode é a unidade de efeito mais básica, mas também há um delay, um convolver, um filtro biquadrático, um estéreo panner, um wave shaper e muitos outros. Você pode obter novos efeitos de bibliotecas como Tone.js.

Armazenar um desses patches de som em objetos próprios permitirá reutilizá-los conforme necessário e criar orquestrações mais complexas com menos código. Isso pode ser assunto para um próximo post.

Facilitando as coisas com o Tone.js

Agora que demos uma breve olhada em como os módulos de áudio da Web vanilla funcionam, vamos dar uma olhada no incrível framework de áudio da Web: Tone.js. Com isso (e o NexusUI para componentes de interface do usuário), podemos facilmente construir sintetizadores e sons mais interessantes. Para experimentar, vamos fazer um sampler e aplicar alguns efeitos interativos do usuário nele, e então adicionaremos alguns controles simples para este exemplo.

Amostrador de Tone.js

Podemos começar criando uma estrutura de projeto simples:

 simpleSampler |-- js |-- nexusUI.js |-- Tone.js |-- noisecollector_hit4.wav |-- sampler.html

Nossas bibliotecas JavaScript residirão no diretório js . Para os propósitos desta demonstração, podemos usar o arquivo hit4.wav do NoiseCollector que pode ser baixado de Freesound.org.

O Tone.js fornece suas funcionalidades por meio de objetos Player. A capacidade básica do objeto é carregar uma amostra e reproduzi-la em um loop ou uma vez. Nosso primeiro passo aqui é criar um objeto player em uma var “sampler”, dentro do arquivo sampler.html:

 <!doctype html> <html> <head> <title> Sampler </title> <script type="text/javascript" src="js/nexusUI.js" ></script> <script type="text/javascript" src="js/Tone.js" ></script> <script> var sampler = new Tone.Player("noisecollector_hit4.wav", function() { console.log("samples loaded"); }); </script> </head> <body> </body> </html>

Observe que o primeiro parâmetro do construtor do player é o nome do arquivo WAV e o segundo é uma função de retorno de chamada. WAV não é o único tipo de arquivo suportado, e a compatibilidade depende mais do navegador da web do que da biblioteca. A função de retorno de chamada será executada quando o player terminar de carregar a amostra em seu buffer.

Também temos que conectar nosso amostrador à saída. A maneira Tone.js de fazer isso é:

 sampler.toMaster();

… onde sampler é um objeto Tone.Player, após a linha 10. A função toMaster é um atalho para connect(Tone.Master).

Se você abrir o navegador da Web com o console do desenvolvedor aberto, deverá ver a mensagem “amostras carregadas”, indicando que o player foi criado corretamente. Neste ponto, você pode querer ouvir a amostra. Para fazer isso, precisamos adicionar um botão à página da Web e programá-lo para reproduzir a amostra uma vez pressionada. Vamos usar um botão NexusUI no corpo:

 <canvas nx="button"></canvas>

Agora você deve ver um botão arredondado sendo renderizado no documento. Para programá-lo para reproduzir nosso exemplo, adicionamos um ouvinte NexusUI, que se parece com isso:

 button1.on('*',function(data) { console.log("button pressed!"); })

Algo notável sobre o NexusUI é que ele cria uma variável global para cada elemento do NexusUI. Você pode configurar o NexusUI para não fazer isso e, em vez disso, ter essas variáveis ​​apenas em nx.widgets[] configurando nx.globalWidgets como false. Aqui vamos criar apenas alguns elementos, então vamos nos ater a esse comportamento.

Assim como no jQuery, podemos colocar esses eventos .on, e o primeiro argumento será o nome do evento. Aqui estamos apenas atribuindo uma função ao que for feito no botão. Este seja o que for escrito como “*”. Você pode saber mais sobre eventos para cada elemento na API NexusUI. Para reproduzir o sample em vez de registrar mensagens quando pressionamos o botão, devemos executar a função start do nosso sampler.

 nx.onload = function() { button1.on('*',function(data) { console.log("button pressed!"); sampler.start(); }); }

Observe também que o ouvinte entra em um retorno de chamada onload. Os elementos NexusUI são desenhados na tela e você não pode se referir a eles até que nx chame a função onload. Assim como você faria com elementos DOM em jQuery.

O evento é acionado ao pressionar o mouse e ao soltar. Se você quiser que ele seja acionado apenas na prensa, você deve avaliar se event.press é igual a um.

Com isso, você deve ter um botão que reproduz a amostra em cada pressionamento. Se você definir sampler.retrigger como true, ele permitirá que você toque a amostra independentemente de estar tocando ou não. Caso contrário, você terá que esperar até que a amostra termine para reativá-la.

Aplicando efeitos

Com o Tone.js, podemos facilmente criar um atraso:

 var delay= new Tone.FeedbackDelay("16n",0.5).toMaster();

O primeiro argumento é o tempo de atraso, que pode ser escrito em notação musical como mostrado aqui. O segundo é o nível molhado, que significa a mistura entre o som original e o som que tem efeito sobre ele. Para delays você normalmente não quer um 100% molhado, porque os delays são interessantes em relação ao som original, e o molhado sozinho não é muito atraente como os dois juntos.

O próximo passo é desconectar nosso sampler do master e conectá-lo ao delay. Ajuste a linha onde o sampler está conectado ao master:

 sampler.connect(delay);

Agora tente o botão novamente e veja a diferença.

Em seguida, adicionaremos dois mostradores ao corpo do nosso documento:

 <canvas nx="dial"></canvas> <canvas nx="dial"></canvas>

E aplicamos os valores dos mostradores ao efeito de atraso usando o NexusUIlistener:

 dial1.on('*',function(data) { delay.delayTime.value=data.value; }) dial2.on('*',function(data) { delay.feedback.value=data.value; })

Os parâmetros que você pode ajustar em cada evento podem ser encontrados nas documentações do Tone.js. Para atraso, está aqui. Agora você está pronto para experimentar o exemplo e ajustar os parâmetros de atraso com os mostradores NexusUI. Esse processo pode ser feito facilmente com cada elemento NexusUI, não se limitando apenas aos efeitos. Por exemplo, tente também adicionar outro dial e adicionar seu ouvinte da seguinte maneira:

 dial3.on('*',function(data) { sampler.playbackRate=data.value; })

Você pode encontrar esses arquivos em github.com/autotel/simpleSampler

Conclusão

Quando passei por essas APIs, comecei a me sentir sobrecarregado com todas as possibilidades e ideias que começaram a vir à minha mente. A grande diferença entre essa implementação de áudio e as implementações tradicionais de áudio digital não está no áudio em si, mas no contexto. Não há novos métodos de síntese aqui. Em vez disso, a inovação é que a produção de áudio e música está agora atendendo às tecnologias da web.

Estou pessoalmente envolvido na produção de música eletrônica, e essa área sempre teve esse paradoxo da ambiguidade entre realmente tocar música e apenas pressionar o play em uma faixa gravada. Se você quer realmente fazer música eletrônica ao vivo, você deve ser capaz de criar suas próprias ferramentas performáticas ou “robôs de fazer música” para improvisação ao vivo. Mas se a performance da música eletrônica se tornar simplesmente ajustar parâmetros em algoritmos pré-preparados para fazer música, então o público também pode se envolver nesse processo. Tenho trabalhado em pequenos experimentos sobre essa integração de web e áudio para música crowdsourced, e talvez em breve estejamos participando de festas onde a música vem do público através de seus smartphones. Afinal, não é tão diferente das jams rítmicas que poderíamos ter apreciado nas eras das cavernas.


Leitura adicional no Blog da Toptal Engineering:

  • Tutorial WebAssembly/Rust: Processamento de áudio perfeito
  • Tutorial MIDI: Criando aplicativos de áudio baseados em navegador controlados por hardware MIDI