Analizador de sonido (4)

facebook-svg gplus-svg twitter-svg

Podemos arrastrar y soltar un archivo de nuestro ordenador directamente en el navegador, y dejar al navegador que utilice este archivo, que puede ser un archivo de sonido, una imagen, u otro tipo de archivos.
A continuación quiero explicar un caso en concreto: quiero coger un archivo de sonido que tengo en el escritorio y arrastrarlo dentro de un elemento <canvas>.
El <canvas> recupera el archivo, y el programa crea un analizador de sonido y utiliza los datos (dataArray) para crear una visualización en canvas: una animación que se mueve al ritmo de la música.

Para empezar en el HTML tengo un elemento <canvas> con un borde de líneas discontinuas y un párrafo que dice "Suelte aquí un archivo de sonido".


<p>Suelte aquí un archivo de sonido</p>
<canvas id="canvas"></canvas>		

En el JavaScript primero declaramos algunas variables necesarias e inicializamos el <canvas>:


// variables para el A U D I O
var audioCtx = new (window.AudioContext || window.webkitAudioContext)();
var analizador = audioCtx.createAnalyser();
analizador.fftSize = 64; // [32, 64, 128, 256, 512, 1024, 2048]
var dataArray = new Uint8Array(analizador.frequencyBinCount);
var url, audio, audiofile, fuenteDeReproduccion;

// inicializa el C A N V A S
var canvas = document.querySelector("canvas");
var p = document.querySelector("p");
var ctx = canvas.getContext("2d");
var cw = (canvas.width = 700),
  cx = cw / 2;
var ch = (canvas.height = 300),
  cy = ch / 2;
ctx.fillStyle = "hsla(210,95%,45%,.75)";        

Por favor lea más acerca de cómo crear un analizador de sonido, y acerca de como inicializar el canvas

El evento drag

Si arrastramos un archivo de sonido de nuestro ordenador en el navegador este se abre en una página distinta. ¡Compruébelo!

No nos interesa que esto pase. Así que tenemos que impedir al navegador que haga lo que hace por defecto. Para esto utilizamos los métodos event.stopPropagation() y event.preventDefault().

Los eventos a utilizar en este caso son:
- dragenter ( cuando la imagen que arrastramos entra en el canvas ),
- dragover ( cuando arrastramos la imagen por encima del canvas ), y
- drop ( cuando soltamos la imagen ).

Pero hay toda una lista de eventos que pueden ser utilizados en esta situación.


  canvas.addEventListener("dragenter", dragenter, false);
  canvas.addEventListener("dragover", dragover, false);
  canvas.addEventListener("drop", drop, false);
  
function dragenter(e) {
    e.stopPropagation();
    e.preventDefault();
  }
  
function dragover(e) {
    e.stopPropagation();
    e.preventDefault();
  }
  
function drop(e) {
    e.stopPropagation();
    e.preventDefault();
  }

Véalo en codepen

See the Pen Drag your file here A by Gabi (@enxaneta) on CodePen.

Ahora si soltamos un archivo de sonido, o cualquier otro tipo de archivo, justo encima del canvas no pasa nada, y es justo lo que queremos. Sin embargo si soltamos el audio fuera del canvas el navegador lo abrirá en otra página.

Para poder manejar los archivos necesitamos saber más, y para esto tenemos recuperar los datos del audio. El objeto dataTransfer guarda los datos de los archivos que soltamos en el canvas, y dataTransfer.files contiene una lista de los archivos soltados.

function drop(e) {
    e.stopPropagation();
    e.preventDefault();
  
    var datos = e.dataTransfer;
    var archivos = datos.files;
    // ahora podemos manejar los archivos:
    manejarArchivos(archivos);
  }

La función manejarArchivos()

Primero utilizamos un bucle for para iterar todos los archivos soltados en el canvas.

function manejarArchivos(archivos) {
for (var i = 0; i < archivos.length; i++) {
      var audioFile = archivos[i];
      . . . . .

Para cada archivo necesitamos comprobar si se trata de un audio. El MIME Type de un archivo de sonido empieza con "audio/" asi que podemos utilizar expresiones regulares para verificar si se trata de un audio o no, e ignorarlo si no lo es.

// si es un archivo de sonido empieza con "audio/"
var esAudio = /^audio\//;      
// si no esAudio ignoralo!
if (!esAudio.test(archivo.type)) {
        continue;
 }

Pero si es un archivo de sonido podemos reproducir el audio, crear un analizador de sonido para la visualización y crear la animación. Para todo esto voy a escribir tres funciones: reproducirAudio(), visualizarSonido() y Animacion().


function manejarArchivos(archivos) {
  for (var i = 0; i < archivos.length; i++) {
    audioFile = archivos[i];
    // si es un archivo de sonido empieza con "audio/"
    var esAudio = /^audio\//;
    // si no esAudio ignoralo
    if (!esAudio.test(audioFile.type)) {
      continue;
    }
    url = window.URL.createObjectURL(audioFile);
    reproducirAudio();
    visualizarSonido();
    Animacion();
  }
}

La función reproducirAudio()

Queremos que el navegador empiece a reproducir el audio inmediatamente, y sin solicitar permiso ( o sea sin que el usuario tenga que hacer clic en algún botón ). Para esto:

audio.autoplay = true;

Para reproducir el audio esta vez tenemos que utilizar el método play().

audio.play();

También queremos recuperar el nombre del archivo de sonido para sacarlo en pantalla.

var name = audioFile.name;
p.innerHTML = name;

Finalmente utilizamos el evento media "onended" para detectar cuando se acaba la reproducción del sonido. Si la reproducción se ha acabado cambiamos el mensaje que sacamos en pantalla y llamamos el método URL.revokeObjectURL()  para comunicar al navegador que hemos acabado y ya no es necesario guardar la información acerca del archivo en memoria.

audio.onended = function() {
      p.innerHTML = "Suelte un archivo de sonido aqu&iacute;";
      window.URL.revokeObjectURL(audio.src);
};

function reproducirAudio() {
  // crea un nuevo elemento Audio 
  audio = new Audio();
  audio.autoplay = true;
  audio.src = window.URL.createObjectURL(audioFile);
  audio.play();
  // recupera el nombre del archivo de sonido y lo saca en pantalla 
  var name = audioFile.name;
  p.innerHTML = name;
  // cuando la reproducción se acaba
  audio.onended = function() {
    p.innerHTML = "Suelte un archivo de sonido aquí";
    window.URL.revokeObjectURL(audio.src);  
  };
}

La función visualizarSonido()

La función visualizarSonido() crea una fuente de reproducción ( fuenteDeReproduccion ), conecta la fuenteDeReproduccion con el analizador y el analizador con el dispositivo de destino ( audioCtx.destination ).


function visualizarSonido() {
  fuenteDeReproduccion = audioCtx.createMediaElementSource(audio);
  fuenteDeReproduccion.connect(analizador);
  analizador.connect(audioCtx.destination);
}

Por favor lea más acerca de cómo crear un analizador de sonido

La función Animacion()

Primero tenemos que crear el array de barras que queremos animar.

        
// crea el array de las barras
var barras = [];
var bNum = 32; // el número de barras
// crea una por una las barras y las agrega al final del array
for (var i = 0; i < bNum; i++) {
  var barra = {};
  barra.w = cw / bNum;
  barra.h = 0;
  barra.x = i * barra.w;
  barra.y = ch;
  barras.push(barra);
}

La función Animacion() es practicamente igual a la función Fotograma() de este otro ejemplo: Un analizador de sonido


function Animacion() {
  requestId = window.requestAnimationFrame(Animacion);
  /*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);
  }
}

Véa este ejemplo en codepen

See the Pen Suelte un archivo de sonido aquí by Gabi (@enxaneta) on CodePen.