Visualizador de Áudio
Aqui vamos explorar algumas das propriedades de áudio da API e mostrar o básico de criação de visualizadores de áudio. A maioria dos visualizadores é construída a partir do mesmo conjunto de princípios básicos, então um pouco de prática pode produzir resultados bem divertidos.
Propriedades de Áudio do SignalRGB
Seção intitulada “Propriedades de Áudio do SignalRGB”Os dados de áudio fornecidos pelo SignalRGB podem ser acessados por meio de algumas propriedades no seu código:
- engine.audio.level - retorna um número entre -100 e 0 representando o volume geral da faixa. 0 é alto, -100 é muito baixo.
- engine.audio.density - retorna um número entre 0 e 1 representando a aspereza do tom, com tons de teste retornando 0 e ruído branco retornando 1.
- engine.audio.freq - retorna um array de 200 elementos contendo os dados de frequência da faixa.
Cada propriedade requerirá diferentes níveis de normalização ou ajuste antes de poder ser utilizada adequadamente, o que abordarei em breve.
Vamos começar com a animação básica de frequência.
Frequência
Seção intitulada “Frequência”A frequência representa o tom do som que ouvimos e é a propriedade mais importante para visualizadores de áudio. O que fazemos aqui é pegar 200 fatias da onda de frequência a cada frame e convertê-la para forma visual. O processo básico é:
- Instanciar um array e preenchê-lo com os dados de frequência.
- Editar esse array conforme suas necessidades (filtrar, mapear, reduzir, etc.).
- Escrever uma classe de “barra de som” para representar cada elemento.
- Conectar os dados à classe de barra de som a cada frame.
O importante com a frequência é que precisaremos fazer dois ajustes nos dados brutos. Às vezes um elemento virá com um valor negativo, o que é visualmente indesejável. A altura dos elementos também chega incorreta para este exemplo. Como valores positivos no parâmetro “height” de um retângulo desenham para baixo a partir da origem da forma, vamos querer inverter isso para se parecer com um visualizador comum.
Exemplo — dados não processados:

Dados processados:

Exemplo de código com dados processados:
<head> <title>Visualizer Tutorial</title> <meta description="Basic Effects" /> <meta publisher="SignalRgb" /> </head>
<body style="margin: 0; padding: 0; background: #000;"> <canvas id="exCanvas" width="320" height="200"></canvas> </body>
<script> canvas = document.getElementById('exCanvas'); ctx = canvas.getContext('2d'); var effects = []; var reducedFreq = [];
function update() {
// "frequency" represents the full 200 elements from our frequency data var frequency = new Int8Array(engine.audio.freq) // "reducedFreq" filters the data down to every fourth result to save some CPU load reducedFreq = frequency.filter((element, index) => { return index % 4 === 0; })
// Create the effect if it does not yet exist if(effects.length < 1){ effects.push(new soundBars(20, 100)) }
// Background color DrawRect(0, 0, 320, 200, "black")
// Play effect effects.forEach((ele, i) => { ele.draw(); if (ele.lifetime <= 0) { effects.splice(i, 1); } });
window.requestAnimationFrame(update); }
function soundBars (x, y){ this.x = x; this.y = y; this.draw = function(){ for(let i = 0; i < reducedFreq.length; i++){ var x = this.x + 5 * i var y = this.y // Data processing occurs for "height". Find the absolute value of each element, then flip negative var height = -Math.abs(reducedFreq[i]) DrawRect(x, y, 5, height, "white") } } }
function DrawRect(x, y, width, height, color) { ctx.beginPath(); ctx.fillStyle = color; ctx.fillRect(x, y, width, height); };
window.requestAnimationFrame(update); </script>Ainda há alguns problemas com o visualizador acima. Embora a música soe bem equilibrada pelos nossos fones de ouvido, podemos ver que os dados favorecem massivamente algumas das nossas barras de som e frequentemente deixam outras completamente invisíveis. De uma perspectiva artística, isso não é ideal, então vamos “normalizar” os dados recebidos do SignalRGB. A normalização envolve distribuir uniformemente os dados entre um ponto máximo e mínimo para ilustrar melhor seu valor em relação uns aos outros. Após descobrir nossos valores máximos e mínimos no array de frequência, a equação é bem simples no geral: (x - min) / (max - min), onde “x” representa o elemento atual.
Dados normalizados e processados:

A seguir, vamos adicionar um toque especial organizando as barras de som em um círculo:
function soundBars (x, y){ this.x = x; this.y = y;
this.draw = function(){
var max = Math.max(...reducedFreq) var min = Math.min(...reducedFreq)
for(let i = 0; i < reducedFreq.length; i++){ //Save the current state of the canvas ctx.save() //Add the circular component to your bars. "50" here is the radius of the circle var x = this.x + Math.cos(i) * 50 var y = this.y + Math.sin(i) * 50 var height = ((Math.abs(reducedFreq[i])) - min) / (max - min) * -40 // Determine the angle of the bar, to make it perpindicular to the circle var rotate = Math.atan2(y - this.y, x - this.x) + Math.PI / 2; //Translate to circle center ctx.translate(x, y) //Rotate the canvas ctx.rotate(rotate) //Translate back for drawing ctx.translate(-x, -y) DrawRect(x, y, 5, height, "white") //Restore the old state of the canvas after drawing to prevent positive feedback loops ctx.restore() } } }
Densidade
Seção intitulada “Densidade”A densidade é simples de usar. O valor retornado será um número entre 0 e 1 representando a “limpeza” do tom. Tons digitais ficarão mais próximos de 0, e tons analógicos ficarão mais próximos de 1. Neste exemplo, vou usá-la para editar a cor das barras de som, o que dará a tons distintos na sua música colorações distintas.
function soundBars (x, y){
this.x = x; this.y = y; this.rotate = 0;
this.draw = function(){ //Hue is calculated each frame, the resulting amount will be a proper hue option for hsl var hue = engine.audio.density * 360
var max = Math.max(...reducedFreq) var min = Math.min(...reducedFreq) for(let i = 0; i < reducedFreq.length; i++){ ctx.save() var x = this.x + Math.cos(i) * 50 var y = this.y + Math.sin(i) * 50 //Height slightly edited for visability var height = ((Math.abs(reducedFreq[i])) - min) / (max - min) * -50 - 5 this.rotate = Math.atan2(y - this.y, x - this.x) + Math.PI / 2; ctx.translate(x, y) ctx.rotate(this.rotate) ctx.translate(-x, -y) //Insert the edited hue value each frame DrawRect(x, y, 5, height, `hsl(${hue}, 100%, 50%)`) this.rotate = 0; ctx.restore() } } }
Esta propriedade simplesmente retorna o volume da faixa em decibéis, com o detalhe de que produz números entre -100 e 0. -100 é muito silencioso e 0 é muito alto. Precisaremos fazer uma pequena edição nesses dados para usá-los da forma que quero, e vou desenhar essa forma na nossa função update. Aqui, o nível da faixa editará a luminosidade do círculo preto interno.
function update() {
var frequency = new Int8Array(engine.audio.freq) reducedFreq = frequency.filter((element, index) => { return index % 4 === 0; })
if(effects.length < 1){ effects.push(new soundBars(160, 100)) }
DrawRect(0, 0, 320, 200, "black")
effects.forEach((ele, i) => { ele.draw(); if (ele.lifetime <= 0) { effects.splice(i, 1); } });
// Hue calcualtion to match the rest of the visualizer var hue = engine.audio.density * 360 //The level has been multiplied by 10, then added to 100. This is arbitrary, but some combination //of actions here will give you the result you want DrawCircle(160, 100, 50, `hsl(${hue}, 100%, ${100 + engine.audio.level * 10}%)`)
window.requestAnimationFrame(update); }