Analizador de sonido (2)

facebook-svg gplus-svg twitter-svg

Analizadores de sonido (2)

Podemos dar al usuario la posibilidad de utilizar un archivo audio de su ordenador para crear un analizador de sonido. Un ejemplo muy básico servirá para entender como hacerlo. El HTML es muy sencillo: un formulario que contiene el input type='file'.


<form class="boton" id="form">
  <input id='audiofile' type='file'>
  <label id="label" for="audiofile">Elegir archivo</label>
</form>
<canvas></canvas>

En el JavaScript primero declaramos las variables necesarias para el audio y para el canvas:


  // variables para el audio
  var audioCtx = new (window.AudioContext || window.webkitAudioContext)();
  var audio, analizador, fuenteDeReproduccion, dataArray;
  // variables para el canvas
  var canvas = document.querySelector("canvas"); 
  var ctx = canvas.getContext("2d");
  var cw = canvas.width = 700;
  var ch = canvas.height = 300;
  ctx.fillStyle = "white";

El evento onchange

Vamos a utilizar el evento onchange para detectar cuando cambia el valor del input #audiofile, o sea cuando el usuario haya decidido que archivo de sonido quiere reproducir. La estructura de la función de retrollamada es la siguiente:
- crea un nuevo elemento <audio>
- si el audio puede ser reproducido . . . haz algo.
- si la reproducción se ha acabado . . . haz otra cosa.

audiofile.addEventListener("change", function(event) {
       . . . . . 
       // crea un nuevo elemento audio
       audio = new Audio();
       . . . . .
       //si el audio puede ser reproducido . . . haz algo.
       audio.addEventListener("canplay", function() {. . .}, false);
       // si la reproducción se ha acabado . . . haz otra cosa.
       audio.addEventListener("ended", function() {. . .}, false);
}
Crear un nuevo elemento <audio>

Primero tenemos que verificar si se trata de un archivo de sonido.

if(event.target.files[0].type.search("audio") == 0){. . .

Y si lo es, creamos un nuevo elemento <audio> que no pertenece al DOM ( Document Object Model ) y por lo tanto no es visible.

audio = new Audio();

y cuya fuente ( src ) es el BLOB ( acrónimo de Binary Large OBject - objeto binario grande ) del audio escogido por el usuario.

audio.src = URL.createObjectURL(event.target.files[0]);

En principio puede haber más de un archivo de sonido, así que tenemos que indicar que se trata del primero: event.target.files[0].

Lea más acerca de URL.createObjectURL

El evento canplay

Después de haber creado el nuevo elemento <audio> utilizamos el evento media "canplay" ( listo para reproducir ) para detectar si archivo de sonido puede ser reproducido.

audio.addEventListener("canplay", function() {. . .

Y si puede reproducirse, cambiamos el display de label a "none". La etiqueta <label> ya no está visible.

label.style.display = "none";

A continuación creamos un analizador de sonido exactamente como lo hicimos en este articulo: Un analizador de sonido

analizador = audioCtx.createAnalyser();
analizador.fftSize = 256; // [32, 64, 128, 256, 512, 1024, 2048]
dataArray = new Uint8Array(analizador.frequencyBinCount);

Para crear la fuente de reproducción esta vez utilizamos el método createMediaElementSource, que es el método que se utiliza para crear una nueva fuente de reproducción si hay un elemento <audio> que puede reproducirse.

fuenteDeReproduccion = audioCtx.createMediaElementSource(audio);

Conectamos la fuente de reproducción con el analizador, y el analizador con el dispositivo de destino.

fuenteDeReproduccion.connect(analizador);
analizador.connect(audioCtx.destination);

Para reproducir el audio esta vez utilizamos el método play().

audio.play();

Finalmente llamamos la función que genera la animación.

Animacion();
El evento onended

Utilizamos el evento media "ended" para detectar cuando se acaba la reproducción del sonido. Si la reproducción se ha acabado hay que cancelar la animación:

window.cancelAnimationFrame(requestId);

limpiar el canvas:

ctx.clearRect(0, 0, cw, ch);

y cambiar el display de label a "block", ya que durante la animación el valor de la propiedad display de la etiqueta es "none".

label.style.display = "block";

Si probamos cargar el mismo archivo dos veces seguidos, la segunda vez el evento "onchange" de input[type=file] no se dispara. ( No pasa lo mismo si la segunda vez cargamos otro archivo.) Para que esto no pase tenemos que restablecer ( reset ) el formulario:

form.reset();

audiofile.addEventListener("change", function(event) {
  // si se trata de un archivo de sonido
  if(event.target.files[0].type.search("audio") == 0){
  // crea un nuevo elemento 

La animación

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


function Animacion() {
    requestId = window.requestAnimationFrame(Animacion);
    ctx.clearRect(0, 0, cw, ch);
    analizador.getByteTimeDomainData(dataArray);
    // alternativamente puede utilizar el método getByteFrequencyData
    
    // utiliza un bucle for ( for loop ) para leer los datos de dataArray y dibujar un pequeño circulo para cada elemento del array
    for (var i = 0; i < dataArray.length; i += 1) {
      // calcula la posición y tamaño de cada circulo
      var w = cw/dataArray.length;
      var x = i * w;
      var y = dataArray[i];
      // dibuja el circulo
      ctx.beginPath();
   }
}
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 getByteTimeDomainData() toma los datos del analizador, los normaliza y actualiza el valor de cada elemento de dataArray en función del valor de la frequencia.

Consejo: pruebe utilizar el método getByteFrequencyData() en lugar de getByteTimeDomainData() para ver la diferencia.

analizador.getByteTimeDomainData(dataArray);

A continuación un bucle for ( for loop ) lee los datos de dataArray, y calcula la posición de cada circulo ( ¡Es aquí donde los artistas pueden hacer virguerías! )

var w = cw/dataArray.length;
var x = i * w;
var y = dataArray[i];

y dibuja un pequeño circulo para cada elemento del array:

ctx.beginPath();
ctx.arc(x, y, (w/2 - 1), 0, 2 * Math.PI);
ctx.fill();

Vea este ejemplo en codepen:

Elija un archivo audio de su ordenador y pruebe este ejemplo:

See the Pen Web Audio Api: elegir archivo by Gabi (@enxaneta) on CodePen.