Clases de objetos audio
Ahora que ya sabemos reproducir archivos de sonido, veamos como podemos utilizar clases de objetos para poder trabajar con varios audios a la vez.
En el siguiente ejemplo hay 26 ficheros audio de notas musicales. El URL de estos ficheros es muy parecido, son todos archivos .wav
que se encuentran en la misma carpeta, algo así:
https://donde/se/encuentra/la/carpeta/1.wav https://donde/se/encuentra/la/carpeta/1.5.wav https://donde/se/encuentra/la/carpeta/2.wav . . . . . .
En ES6 para crear cadenas de texto podemos emplear las `comillas invertidas` que nos permiten utilizar variables en la cadena de texto, y lo podemos hacer utilizando el símbolo dólar y poniendo el nombre de la variable entre llaves: ${variable}
. O sea: podemos crear el url de un fichero audio de la siguiente manera:
`https://donde/se/encuentra/la/carpeta/${nombreFichero}.wav`
Lea más acerca de cadenas de texto y plantillas literales en ES6
En el JavaScript primero vamos a abordar la reproducción de sonido, y después en el DOM vamos a crear los botones y los eventos necesarios para la manipular el sonido.
La reproducción del sonido
Para la reproducción de sonido, en el JavaScript primero habrá que crear un nuevo contexto audio:
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
También necesitamos crear un array con todos los nombres de archivos de sonido, que vamos a utilizar para construir el URL.
const notas = [1,1.5,2,2.5,3,3.5,4,4.5,5,5.5,6,6.5,7,8,8.5,9,9.5,10,11,11.5,12,12.5,13,13.5,14,15];
// notas.length = 26
Crear la clase teclaPiano
Aquí viene lo más importante: crear una clase de objetos audio.
El primer método de una clase en ES6 es el constructor
, que en este caso toma como atributo el url
del archivo de sonido correspondiente.
class teclaPiano {
constructor(url) {
this.url = url;
. . . .
}
También necesitamos algunas propiedades más para el audio buffer y para la fuente de reproducción; y otra propiedad ( this.stop
) para controlar la reproducción y la detención del archivo de sonido. El valor inicial de this.stop = true
, porque al inicio el archivo de sonido está parado.
class teclaPiano { constructor(url) { this.url = url; this.fuenteDeReproduccion; this.audioBuffer; this.stop = true; }
El método solicitarAudio
El método solicitarAudio
es muy parecido a la función solicitarAudio
de los ejemplos anteriores. Sin embargo como que se trata de un objeto vamos a utilizar las propiedades del objeto, o sea: this.url
y this.audioBuffer
. Y esto es un pequeño problema:
request.open("GET", this.url, true);
Si utilizamos this.url
para abrir la solicitud ( request
), this
se refiere al objeto request
, y no al objeto teclaPiano
. Para eludir este problema vamos a guardar el objeto teclaPiano
en una variable:
let _this = this;
Ulteriormente utilizamos esta variable ( _this
) para recuperar las propiedades del audio ( el objeto teclaPiano
).
solicitarAudio() { let _this = this; let request = new XMLHttpRequest(); request.open("GET", _this.url, true); request.responseType = "arraybuffer"; request.onload = function() { audioCtx.decodeAudioData(request.response, function(buffer) { _this.audioBuffer = buffer; }); }; request.send(); }
El método reproducir Audio()
A diferencia de la función con el mismo nombre de los ejemplos anteriores esta vez necesitamos detener el audio cuando la reproducción se acaba ( this.fuenteDeReproduccion.onended
). Y de nuevo habrá que utilizar el truquillo del let _this = this;
reproducirAudio() { let _this = this; this.stop = false;// el audio no está parado this.fuenteDeReproduccion = audioCtx.createBufferSource(); if (this.audioBuffer) { this.fuenteDeReproduccion.buffer = this.audioBuffer; } this.fuenteDeReproduccion.connect(audioCtx.destination); this.fuenteDeReproduccion.start(audioCtx.currentTime); this.fuenteDeReproduccion.onended = function() { _this.detenerAudio();// un método que tenemos que escribir }; }
El método detenerAudio()
También necesitamos un método para parar la reproducción que ya hemos utilizado anteriormente.
detenerAudio() {
this.fuenteDeReproduccion.stop();
this.stop = true;// el audio está parado
}
Crear los objetos tecla
A continuación el programa crea una por una los objetos tecla, solicita el audio para el nuevo objeto y lo guarda en el array teclas
for (let i = 0; i < notas.length; i++) { // crea una por una las teclas let tecla = new teclaPiano( `https://s3-us-west-2.amazonaws.com/s.cdpn.io/222579/${notas[i]}.wav` ); // solicita el audio tecla.solicitarAudio(); // guarda el objeto creado en el array de las teclas teclas.push(tecla); }
El DOM
Para tocar cada audio en parte en el DOM vamos a crear 26 botones, uno para cada tecla.
let botones = [];// el array de los botones for (let i = 0; i < notas.length; i++) {// para cada nota musical let b = document.createElement("button");// crea un nuevo botono b.textContent = notas[i]; // establece el texto de cada boton piano.appendChild(b);// agrega el boton al documento botones.push(b);// agrega el boton al array de botones }
Al pasar con el raton por encima de cada botón, quiero que el color del texto cambie de negro a rojo. Por ejemplo para el primer botón ( botones[0]
) podemos escribir:
botones[0].addEventListener("mouseover", function(){this.style.color = "red"}) botones[0].addEventListener("mouseout", function(){this.style.color = "black"})
En el ES6 es posible utilizar funciones flecha, unas funciones anónimas con una sintaxis simplificada. La mala noticia es que, al utilizar funciones flecha, el significado de this
cambia. Pruebe lo siguiente y no olvide abrir la consola de su navegador:
divRojo.addEventListener("click", function() { console.log(this);// this se refiere al divRojo }); divRojo.addEventListener("click", ()=>{ console.log(this);// this se refiere al objeto window });
En conclusión: al utilizar funciones flecha ya no puedo utilizar la palabra clave this
. En este caso tengo que utilizar botones[0]
.
botones[0].addEventListener("mouseover", () => { botones[0].style.color = "red"}, false) botones[0].addEventListener("mouseout", () => { botones[0].style.color = "black"}, false)
Y si el audio esta parado quiero tambien reproducir el audio en mouseover
:
botones[0].addEventListener( "mouseover", () => { botones[0].style.color = "red"; if (teclas[0].stop) { teclas[0].reproducirAudio(); } }, false );
Como que botones
es un array podemos utilizar el método map()
para iterar sobre todo el array:
botones.map((b, index) => { b.addEventListener( "mouseover", () => { b.style.color = "red"; if (teclas[index].stop) { teclas[index].reproducirAudio(); } }, false ); }); botones.map(b => { b.addEventListener( "mouseout", () => { b.style.color = "black"; }, false ); });
Ahora al pasar con el ratón por encima de cada botón, el color del texto cambia y, si esta parado, el audio vuelve a reproducirse.
Vea este ejemplo en codepen, y no olvide de pasar con el ratón por encima de los botones.
See the Pen Piano - prueba de sonido* by Gabi (@enxaneta) on CodePen.