Analizador de sonido (3)
Podemos crear un analizador de sonido que utiliza la señal audio de un micrófono. La mala noticia es que por ahora esto no funciona en Opera, Safari e IE. En Edge, Firefox y Chrome se le pide permiso para utilizar el micrófono.
Veamos un ejemplo.
En el HTML tenemos un solo elemento: el <canvas>.
En el JavaScript empezamos declarando algunas variables útiles e inicializamos el <canvas>
.
// variables para el audio
// crea un nuevo contexto audio
var audioCtx = new (window.AudioContext || window.webkitAudioContext)();
var analizador = audioCtx.createAnalyser();// crea un nodo analizador
analizador.fftSize = 128; // [32, 64, 128, 256, 512, 1024, 2048]
var dataArray = new Uint8Array(analizador.frequencyBinCount);
//Â Inicializa el canvas
var canvas = document.querySelector("canvas");
ctx = canvas.getContext("2d");
var cw = canvas.width = 700; // la anchura del canvas
var ch = canvas.height = 350;// la altura del canvas
ctx.fillStyle = "hsla(210,95%,45%,.75)";// el color de relleno
Lea más acerca del nodo analizador en este articulo: Un analizador de sonido
También creamos un array de puntos. Vamos a utilizar estos puntos para visualizar la frecuencia de la señal de sonido.
// crea el array de los puntos
var puntos = [];
var pNum = 25;// número de puntos
var puntoW = cw / pNum;// calcula la distribución de los puntos
 for (var i = 0; i < pNum + 1; i++) {
   var punto = {};
   punto.x = i * puntoW;
   punto.y = ch;
   puntos.push(punto);
 }
El método MediaDevices.getUserMedia
A continuación viene lo importante:
El método MediaDevices.getUserMedia()
solicita permiso para utilizar un dispositivo de entrada, en este caso el micrófono.
Ânavigator.mediaDevices.getUserMedia({ audio: true, video: false }).then . . .
Si el usuario autoriza el uso del micrófono, entonces ( then
) una función anónima puede utilizar la señal audio ( stream ) que viene del micrófono.
De lo contrario, si el usuario prohíbe el uso del micrófono, la señal audio no está disponible, y un bloque catch
captura el error y puede hacer algo con el, como por ejemplo, sacar un mensaje en pantalla.
navigator.mediaDevices
 .getUserMedia({ audio: true, video: false })
 .then(function(stream) {
/* utilice la señal audio (stream) */
   var fuenteDeSonido = audioCtx.createMediaStreamSource(stream);
   fuenteDeSonido.connect(analizador);
   Animacion();
 }).catch(function(err) {
/* gestionar errores */
 txt("E R R O R")
});
Lea más acerca del método AudioContext.createMediaStreamSource()
Lea más acerca del método MediaDevices.getUserMedia()
Analicemos un poco que pasa si el usuario autoriza el uso del micrófono: para capturar la fuente de sonido utilizamos el método createMediaStreamSource
. Después conectamos la fuenteDeSonido
con el analizador
y llamamos la función que genera la animación.
var fuenteDeSonido = audioCtx.createMediaStreamSource(stream); fuenteDeSonido.connect(analizador); Animacion();
La animación
La animación es muy parecida a la de un ejemplo anterior: Un analizador de sonido solo que esta vez, en lugar de dibujar rectángulos, conectamos los puntos con curvas cuadráticas de Bézier.
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 / pNum);
 // un bucle for calcula la coordenada en y de cada punto del array de puntos.
 for (var i = 0; i < puntos.length; i++) {
   puntos[i].y = ch - dataArray[i * n] - 50;
 }
 trazarCurvas(puntos);
 txt("Haz algo de ruido");
}
Lea más acerca del 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.
La función que se encarga de trazar las curvas toma como atributo el array de los puntos:
function trazarCurvas(puntos) {
 ctx.beginPath();
// mueve el puntero al primer punto del array
 ctx.moveTo(puntos[0].x, puntos[0].y);
// une los puntos con curvas de Bézier
 for (var i = 1; i < puntos.length - 2; i++) {
   var x = (puntos[i].x + puntos[i + 1].x) / 2;
   var y = (puntos[i].y + puntos[i + 1].y) / 2;
   ctx.quadraticCurveTo(puntos[i].x, puntos[i].y, x, y);
 }
// dibuja la última curva de Bézier
 ctx.quadraticCurveTo(
   puntos[i].x,
   puntos[i].y,
   puntos[i + 1].x,
   puntos[i + 1].y
 );Â
// cierra el trazado
 ctx.lineTo(cw, ch);
 ctx.lineTo(0, ch);
 ctx.closePath();
// y lo rellena de color
 ctx.fill();
}
Lea más acerca de cómo dibujar Curvas cuadráticas de Bézier en canvas.
También hay una función que se encarga de dibujar el mensaje de texto.
function txt(mensaje) {
 var t = mensaje.split("").join(" ");
 ctx.font = "1.5em Lucida Console";
 ctx.textAlign="center";Â
 ctx.fillText(t, cw*.5, ch*.5);
}
Lea más acerca de cómo dibujar texto en canvas
Puede ver este ejemplo en codepen: Haz algo de ruido (Web audio api)*
También puede consultar aquí el código completo:
// variables para el audio
var audioCtx = new (window.AudioContext || window.webkitAudioContext)();
var analizador = audioCtx.createAnalyser();
analizador.fftSize = 128; // [32, 64, 128, 256, 512, 1024, 2048]
var dataArray = new Uint8Array(analizador.frequencyBinCount);
// Inicializa el canvas
var canvas = document.querySelector("canvas");
ctx = canvas.getContext("2d");
var cw = canvas.width = 700; // la anchura del canvas
var ch = canvas.height = 350;// la altura del canvas
ctx.fillStyle = "hsla(210,95%,45%,.75)";
// crea el array de los puntos
var puntos = [];
var pNum = 25;// número de puntos
var puntoW = cw / pNum;// calcula la distribución de los puntos
for (var i = 0; i < pNum + 1; i++) {
var punto = {};
punto.x = i * puntoW;
punto.y = ch;
puntos.push(punto);
}
navigator.mediaDevices
.getUserMedia({ audio: true, video: false })
.then(function(stream) {
var fuenteDeSonido = audioCtx.createMediaStreamSource(stream);
fuenteDeSonido.connect(analizador);
Animacion();
}).catch(function(err) {
txt("E R R O R")
});
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 / pNum);
// un bucle for calcula la coordenada en y de cada punto del array de puntos.
for (var i = 0; i < puntos.length; i++) {
puntos[i].y = ch - dataArray[i * n] - 50;
}
trazarCurvas(puntos);
txt("Haz algo de ruido");
}
function trazarCurvas(puntos) {
ctx.beginPath();
ctx.moveTo(puntos[0].x, puntos[0].y);
for (var i = 1; i < puntos.length - 2; i++) {
var x = (puntos[i].x + puntos[i + 1].x) / 2;
var y = (puntos[i].y + puntos[i + 1].y) / 2;
ctx.quadraticCurveTo(puntos[i].x, puntos[i].y, x, y);
}
ctx.quadraticCurveTo(
puntos[i].x,
puntos[i].y,
puntos[i + 1].x,
puntos[i + 1].y
);
ctx.lineTo(cw, ch);
ctx.lineTo(0, ch);
ctx.closePath();
ctx.fill();
}
function txt(mensaje) {
var t = mensaje.split("").join(" ");
ctx.font = "1.5em Lucida Console";
ctx.textAlign="center";
ctx.fillText(t, cw*.5, ch*.5);
}