Un analizador de sonido

facebook-svg gplus-svg twitter-svg

El método createAnalyser() del AudioContext crea un nodo analizador que puede ser utilizado para crear una representación visual del sonido, por ejemplo en el canvas de HTML5.
Para entender de que va, no hay nada mejor que un ejemplo. A continuación vamos a utilizar el código para reproducir archivos de sonido de este artículo, pero habrá que hacer algunos cambios.
Primero, inmediatamente después de crear el AudioContext, vamos a crear un analizador utilizando el método createAnalyser():

var analizador = audioCtx.createAnalyser();

La propiedad fftSize del nodo analizador  ( abreviado de Fast Fourier Transform Size ) es un número integro positivo que representa el tamaño ( size ) del FFT, un algoritmo que permite muestrear las frecuencias del sonido. Su valor tiene que ser un número al cuadrado entre 32 y 2048, y el valor por defecto es 2048.

analizador.fftSize = 1024;// [32, 64, 128, 256, 512, 1024, 2048]

El número de contenedores ( bins ) disponibles es igual al fftSize/2 y es accesible mediante otra propiedad de sólo-lectura ( read-only ) del analizador: frequencyBinCount.

var dataArray = new Uint8Array(analizador.frequencyBinCount);

El constructor Uint8Array ( abreviado de Unsigned integer 8bits Array )  crea un nuevo objeto, muy parecido a un array de números íntegros positivos de 8bits, o sea de 0 a 255.

Lea más acerca de Uint8Array y otros arrays parecidos: JavaScript typed arrays

Nos interesa Uint8Array porque tenemos que utilizarlo a continuación para crear el array de frecuencias del audio que necesitamos para la animación:

Por ahora este es un array cuya longitud es igual al analizador.frequencyBinCount ( 512 ), pero todos los elementos están iniciados al 0.

También habrá que modificar un poco la función reproducirAudio(), ya que esta vez necesitamos conectar la fuenteDeReproducción con el analizador, y el analizador con el dispositivo de destino ( altavoces, auriculares . . . )

function reproducirAudio() {
      fuenteDeReproduccion = audioCtx.createBufferSource();
      fuenteDeReproduccion.buffer = audioBuffer;
  
      fuenteDeReproduccion.connect(audioCtx.destination);
      fuenteDeReproduccion.connect(analizador)
      analizador.connect(audioCtx.destination);
  
      fuenteDeReproduccion.start(audioCtx.currentTime);
  }

Ahora viene lo del canvas. En este caso he optado por una animación sencilla de barras verticales.

Primero tenemos que configurar el canvas:


    // configura el canvas
    var canvas = document.querySelector("canvas");
    ctx = canvas.getContext('2d');
    var cw = canvas.width = 500;
    var ch = canvas.height = 255;

A continuación habrá que construir el array de barras verticales:


    var barras = [];// crea el array de barras
    var bNum = 25;// el numero de barras verticales
    
  for(var i= 0; i < bNum; i++){
    // crea un nuevo objeto barra
    var barra = {};
    // establece la anchura ( w ) y la altura ( h ) de las barras
    barra.w = cw/bNum;
    barra.h = 0;
    // estallece las coordenadas en x e y de cada barra 
    barra.x = i*barra.w;
    barra.y = ch;
    // añade la barra al final del array barras, utilizando el método push
    barras.push(barra);
  }

Por favor observe que la coordenada en y de las barras ( barra.y ) es igual a la altura del canvas ( ch ).

El siguiente paso es crear la función que genera una nueva fotograma para la animación.


function Fotograma() {
  requestId = window.requestAnimationFrame(Fotograma);
  /*el método getByteFrequencyData() toma como argumento un array de tipo Uint8Array*/
  analizador.getByteFrequencyData(dataArray);
  ctx.clearRect(0, 0, cw, ch);
  // la doble tilde (~~) es un operador equivalente a Math.floor() o casi
  var n = ~~(analizador.frequencyBinCount / bNum);
  for (var i = 0; i < barras.length; i++) {
    barras[i].h = -dataArray[i * n]; // altura negativa!!
    ctx.beginPath();
    ctx.fillRect(barras[i].x, barras[i].y, barras[i].w - 1, barras[i].h);
  }
}
La explicación del código

Esta función utiliza el método requestAnimationFrame, un método que genera animaciones fluidas, que paran en pestañas ( tabs ) inactivas, y es mucho más eficiente en los navegadores.

requestId = window.requestAnimationFrame(Fotograma);

El método getByteFrequencyData() toma los datos del analizador, los normaliza y actualiza el valor de cada elemento de dataArray en función del valor de la frequencia.

analizador.getByteFrequencyData(dataArray);

Lo que viene a continuación no es nada complicado. Simplemente calculamos la altura de las barras en función del valor correspondiente en el dataArray.

Probablemente la única cosa inesperada es que damos a los rectángulos ( barras ) una altura negativa, ya que los dibujamos partiendo de la base del canvas.

 barras[i].h = -dataArray[i * n];// altura negativa!!

Recuerde que la coordenada en y de las barras es barra.y = ch, donde ch representa la altura del canvas.


// ¡Es aquí donde los artistas pueden hacer virguerías!
  var n = ~~(analizador.frequencyBinCount / bNum);
  for (var i = 0; i < barras.length; i++) {
    barras[i].h = -dataArray[i * n]; // altura negativa!!
    ctx.beginPath();
    ctx.fillRect(barras[i].x, barras[i].y, barras[i].w - 1, barras[i].h);
  }

Vea este ejemplo en codepen:

See the Pen Analizador audio en canvas* by Gabi (@enxaneta) on CodePen.