Saltar al contenido principal

Creando un launchpad de sonidos con Javascript

Qué vamos a hacer

Bienvenido al primer episodio de mi nueva serie de artículos, Sortilegios

Sortilegios va a consistir en artículos en los que explique paso a paso cómo crear pequeños proyectos de desarrollo web y programación.

Para empezar, he decidido empezar con los proyectos que se proponen en #Javascript30, 30 mini proyectos para crear con Javascript y aprender a usarlo. Yo no voy a hacerlos todos, mucho menos en 30 días, pero alguno que me parezca interesante si que trataré de hacerlo.

Para el artículo de hoy voy a empezar con el primero de los retos, crear un launchpad o batería de sonidos con Javascript. Si no sabes lo que es es lo que sale en la imagen de aquí de abajo, una serie de pads o botones que al apretarlos emiten sonidos distintos.

Además, ya que estamos vamos a usar CSS Grid para ver un poco cómo lo haría yo.

Este artículo pertenece a la serie de artículos con contenido práctico llamada Sortilegios 🚧

🗺️ Hoja de ruta

Manos a la obra 👷

Estructura de la vista

Yo viendo lo que se quiere crear, lo primero que me planteo es que en el DOM habrá tantos divs como botones tenga el launchpad. Sí, sé lo que estás pensando, que lo suyo sería utilizar la etiqueta button, pero por no resetear estilos, y como es el primer artículo lo vamos a hacer simple esta vez. En próximos episodios prometo hablar de accesibilidad.

Como digo creamos los botones y un div exterior que los contenga.

<div class="launchpad">
  <div class="pad">A</div>
  <div class="pad">S</div>
  <div class="pad">D</div>
  <div class="pad">F</div>
  <div class="pad">G</div>
  <div class="pad">H</div>
  <div class="pad">J</div>
  <div class="pad">K</div>
  <div class="pad">L</div>
</div>

De momento, voy a poner estos estilos a los pads para poder identificarlos:

.pad {
  width: 130px;
  height: 130px;
  background: #d6d7d8;
  border-radius: 20px;
}

CSS grid, qué es y como funciona. Creando filas y columnas

Este es el ejemplo perfecto para aplicar CSS Grid. Se puede aplicar flexbox sin problema, pero recomiendo Grid en este caso.

CSS Grid es un sistema que permite alinear elementos en filas y columnas. La idea es que se pueden configurar filas y columnas a tu gusto desde el elemento padre, en nuestro ejemplo el elemtno con la clase launchpad.

Lo primero con CSS grid es empezar poniendo los estilos de display: grid;. A continuación lo básico es configurar las filas, las columnas o ambas. En nuestro launchpad vamos a definir las dos (filas y columnas).

Hay dos propiedades para esto: grid-template-columns para las columnas y grid-template-rows para las filas.

Ambas propiedades admiten los mismos valores, el tamaño de cada fila o columna. Tienes que meter un espacio de separación para definir cada fila o columna. Se pueden definir en cualquier medida: píxeles, rems, porcentajes, etc.

Además de esas medidas, CSS Grid tiene una medida extra, los fr (free space). Esta medida es muy recomendable porque lo que hace es coger el tamaño restante de las otras columnas, veamos esto aplicado a nuestro ejemplo:

.launchpad {
  width: 410px;
  margin: 0px auto;
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  grid-template-rows: 1fr 1fr 1fr;
  grid-column-gap: 10px;
  grid-row-gap: 10px;
}

Para el launchpad defino que va a tener 3 columnas y 3 filas, en total los 9 pads. Cada fila y cada columna tendrá un tamaño de 1fr por lo que al final todas las filas y todas las columnas ocuparán lo mismo, es decir, se reparten el espacio.

También he añadido dos propiedades de grid nuevas, grid-column-gap y grid-row-gap que lo que hacen es aádir separación entre las columnas y las filas respectivamente, así no salen pegadas.

Por cierto, no hay problema en utilizar varias unidades para cada fila o columna:

grid-template-columns: 100px 32rem 1fr;

En este caso hay dos columnas con tamaños fijos y la última columna que ocupará el resto del ancho que tenga este elemento.

Si por ejemplo quieres 4 columnas y quieres que una ocupe el doble que las demás simplemente tienes que poner:

grid-template-columns: 2fr 1fr 1fr 1fr;

El problema es que si quieres crear 12 columnas, por ejemplo, tienes que escribir 12 veces 1fr. Por suerte CSS grid tiene una sintaxis especial para esto, tanto para filas colo columnas. Lo que hemos creado para el launchpad es equivalente a:

grid-template-columns: repeat(3, 1fr);
grid-template-rows: repeat(3, 1fr);

Con esto ya tenemos 3 filas y 3 columnas de pads en el centro de la pantalla. Vamos a pasar ahora con la lógica de los sonidos.

Lógica de la aplicación

Bien, como hemos dicho queremos que al pulsar en el teclado la tecla indicada en el pad, se escuche el sonido correspondiente a ese pad.

Lo que tengo pensado es lo siguiente:

Vamos con ello, empecemos reconociendo las teclas del teclado:

window.addEventListener("keydown", playSound);

function playSound(e) {
  console.log(e.keyCode);
}

Sí, es así de sencillo. Con el addEventListener indicas que evento quieres capturar y a qué función vas a llamar. Del elemento que te llega del evento puedes sacar qué tecla estás pulsando, el código de la letra y si se ha pulsado con Control por ejemplo.

Si abres la consola del navegador verás que al pulsar una tecla aparece el número correspondiente a la tecla pulsada.

Vamos a ir poniendo los sonidos. Para no tener que subirlos en otra carpeta y tener luego que importarlos, los voy a cargar directamente en el HTML.

He pensado que para relacionar sonido y tecla del teclado, voy a poner dentro de la etiqueta un atributo data-key para poder localizarlo desde Javascript. Normalmente las etiquetas data-loquesea se usan para este tipo de cosas.

<audio
  data-key="a"
  src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/1159990/clap.wav"
></audio>
<audio
  data-key="s"
  src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/1159990/hihat.wav"
></audio>
<audio
  data-key="d"
  src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/1159990/kick.wav"
></audio>
<audio
  data-key="f"
  src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/1159990/openhat.wav"
></audio>
<audio
  data-key="g"
  src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/1159990/boom.wav"
></audio>
<audio
  data-key="h"
  src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/1159990/ride.wav"
></audio>
<audio
  data-key="j"
  src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/1159990/snare.wav"
></audio>
<audio
  data-key="k"
  src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/1159990/tom.wav"
></audio>
<audio
  data-key="l"
  src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/1159990/tink.wav"
></audio>

Los atributos data-* fueron introducidos en HTML5. Básicamente puedes declarar el atributo data con el nombre que quieras, por ejemplo: data-ejemplo="ejemplo". En este ejemplo le he puesto de nombre key para saber que es la tecla a la que pertenece el sonido.

Ahora toca reproducir el sonido al pulsar la tecla:

function playSound(e) {
  const key = e.key.toLowerCase();
  const mySound = document.querySelector(`audio[data-key="${key}"]`);
  if (!mySound) return;
  mySound.currentTime = 0;
  mySound.play();
}

No tiene más. Se declara la variable para pillar el sonido, en este caso mediante querySelector se busca en la vista el tag audio que tenga como data-key la tecla que hemos pulsado. Si no encuentras la tecla simplemente hace return para que no continue. Si la tecla existe entonces se pone el tiempo a cero y se reproduce el sonido.

Animando los pads

Lo único que quedaría es añadir una pequeña animación al pulsar sobre la tecla para que el usuario tenga una respuesta visual. De igual forma que antes, podemos localizar el elemento que ha pulsado el usuario con el teclado para añadirle una clase y poder animarlo:

function playSound(e) {
  const key = e.key.toLowerCase();
  const mySound = document.querySelector(`audio[data-key="${key}"]`);
  const pad = document.querySelector(`.pad[data-key="${key}"]`);

  if (!mySound) return;

  mySound.currentTime = 0;
  mySound.play();
  pad.classList.add("playing");

  setTimeout(() => {
    pad.classList.remove("playing");
  }, 120);
}

También he añadido un timeout para que pasado unos milisegundos se elimine la clase playing del pad y por lo tanto quede reseteado.

En este caso buscamos los elementos que tengan la clase pad con el data-key igual que hemos hecho antes, por lo que añadiremos en la vista el data-key igual que en el los audios.

<div class="launchpad">
  <div data-key="a" class="pad">
    A
  </div>
  <div data-key="s" class="pad">
    S
  </div>
  <div data-key="d" class="pad">
    D
  </div>
  <div data-key="f" class="pad">
    F
  </div>
  <div data-key="g" class="pad">
    G
  </div>
  <div data-key="h" class="pad">
    H
  </div>
  <div data-key="j" class="pad">
    J
  </div>
  <div data-key="k" class="pad">
    K
  </div>
  <div data-key="l" class="pad">
    L
  </div>
</div>

Listo, ahora queda añadir las clases css encargadas de la animación.

Para el look and feel (el estilo) de esta aplicación he pensado que estaría chulo hacer que los pads estuvieran como en relieve, simulando un launchpad real. Con la clase playing los pads se hunden hacia abajo y se cambia el color. Vamos con ello:

.pad {
  width: 130px;
  height: 130px;
  background: #d6d7d8;
  border-radiud: 20px;
  box-shadow: 0 2px 3px #ccc;
  border-radius: 0.5em;
  border-bottom: 0.4em solid #9e9e9e;
  -moz-transition: 0.1s ease-in;
  -webkit-transition: 0.1s ease-in;
  transition: 0.1s ease-in;
}
.playing {
  background: #ccfbff;
  border-bottom-width: 0;
  margin-top: 0.2em;
}

Los pads simplemente llevan un pequeño borde abajo simulando estar en 3D. Cuando se le añade la clase playing se cambia el color del pad, se elimina el borde, y se baja un poco el botón para que parezca que se ha pulsado.

He modificado un poco los estilos para darle el toque final, han quedado algo así:

.launchpad {
  width: 410px;
  padding: 3rem;
  background: #e5e3de;
  margin: 0px auto;
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  grid-template-rows: 1fr 1fr 1fr;
  grid-column-gap: 10px;
  grid-row-gap: 10px;
  font-family: sans;
  font-size: 18px;
  font-weight: 600;
}
.pad {
  width: 130px;
  height: 130px;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;
  background: radial-gradient(#eaedef, #c2c4c6);
  border-radiud: 20px;
  box-shadow: 0 2px 3px #ccc;
  border-radius: 0.5em;
  border-bottom: 0.4em solid #9e9e9e;
  -moz-transition: 0.1s ease-in;
  -webkit-transition: 0.1s ease-in;
  transition: 0.1s ease-in;
}
.playing {
  background: radial-gradient(#cedae8, #d7e6ef);
  border-bottom-width: 0;
  margin-top: 0.2em;
}

El resultado final es este:

(Es una imagen, si lo quieres ver en funcionamiento hecha un vistazo al código fuente)

Demo y código fuente

https://codepen.io/Frostq/pen/vMXvKo

Deberes

Para complementar los artículos de esta serie voy a proponer una serie de deberes por si quieres ampliar:

Conslusiones

Hasta aquí el capítulo de hoy. Se que hoy no hemos visto mucho, pero está bien para empezar con el formato, quiero ir poco a poco.

Hoy hemos aprendido un par de pinceladas de CSS Grid que al menos creo que te serviran para crear este tipo de layouts.