Clases de objetos audio

facebook-svg gplus-svg twitter-svg

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.