Hacer un teclado de piano que se pueda tocar puede ser una gran manera de aprender un lenguaje de programación (además de bastante divertido). Este tutorial te muestra como programar uno usando Vanilla JavaScript sin la necesidad de librerías o frameworks externos.

Aquí está el teclado de piano en JavaScript que hice si quieres ver el producto terminado primero.

Este tutorial asume que tienes un entendimiento básico de JavaScript como lo son las funciones y manejadores de eventos, así como también familiaridad con HTML y CSS. De lo contrario, es totalmente amigable con principiantes y está dirigido a aquellos que quieren mejorar sus habilidades de JavaScript a través del aprendizaje basado en proyectos (¡o si quieres hacer un proyecto genial!).

El teclado de piano que haremos para este proyecto está basado en el teclado sintético dinámicamente generado hecho por Keith William Horwood. Extenderemos el número de teclas disponibles a 4 octavas y asignaremos nuevos atajos de teclado.

Si bien su teclado puede reproducir sonidos de otros instrumentos, nosotros mantendremos las cosas simples y usaremos solo sonidos de piano.

A continuación los pasos a seguir para abordar este proyecto:

1.      Obtén los archivos de trabajo

2.      Asigna atajos de teclado

3.      Genera el teclado

4.      Maneja presiones de teclas

¡Empecemos!

1. Obtén los archivos de trabajo

Este tutorial usará los siguientes archivos:

·        audiosynth.js

·        playKeyboard.js

Como mencioné, basaremos nuestro teclado de piano en el que fue hecho por Keith. Naturalmente, también usaremos parte de su código, que amablemente ha permitido usar con audiosynth.js.

Incorporamos audiosynth.js en playKeyboard.js (mi versión modificada del código de Keith), el cual maneja todo nuestro JavaScript. Este tutorial da una explicación detallada en las siguientes secciones sobre los puntos clave acerca de cómo el código en este archivo crea un teclado de piano que funcione completamente.

Dejamos el archivo audiosynth.js intacto debido a que es solamente responsable de la generación de sonido.

El código en este archivo distingue este teclado de piano de otros encontrados en línea porque usa JavaScript para generar el sonido apropiado dinámicamente cuando el usuario presiona una tecla. Por lo tanto, el código no tiene que cargar archivos externos de audio.

Keith ya nos proporciona una explicación de cómo funciona la generación de sonido en su sitio web, así que no entraremos en detalle aquí.

En pocas palabras, implica usar la función Math.sin() en JS para crear formas de onda sinusoidal y transformarlas para que suenen más como instrumentos reales a través de matemáticas elegantes.

Crea un archivo HTML index y enlaza los archivos JS en la cabecera:

<script src="audiosynth.js"></script>
<script src="playKeyboard.js"></script>

En el cuerpo, podemos crear un elemento <div> vacío para que sirva como "contenedor" del teclado:

<div id="keyboard"></div>

Le damos un nombre de id para poder referenciarlo más adelante cuando creemos el teclado usando JS. Podemos ejecutar nuestro código JS llamándolo en el cuerpo del documento también:

<script type="text/javascript">playKeyboard()</script>

Usamos playKeyboard.js como una función grande. Se ejecutará tan pronto como el navegador llegue a esa linea de código y genere un teclado que funcione completamente en el elemento <div> con id = “keyboard”.

Las primeras líneas de playKeyboard.js configuran la funcionalidad para dispositivos móviles (opcional) y crea un nuevo objeto AudioSynth(). Usamos este objeto para llamar los métodos de audiosynth.js que enlazamos anteriormente. Usamos uno de estos métodos en el inicio para ajustar el volumen del sonido.

En la línea 11, establecemos la posición de C central a la 4ta octava.

2. Asigna atajos de teclado

Antes que generemos el teclado, debemos asignar los atajos de teclado dado que estos determinan cuantas teclas deberán ser generadas.

Originalmente quería tocar las notas de apertura de ‘Für Elise’ así que escogí un rango de 4 octavas para un total de 48 teclas negras y blancas. Esto requirió casi que todas las teclas en mi teclado (de PC) y tú eres libre de incluir menos.

Nota de precaución: No tengo los mejores atajos de teclado así que podría sentirse poco intuitivo cuando realmente intentes tocar. Tal vez este sea el precio de intentar crear un teclado de 4 octavas.

Para asignar los atajos de teclado, primero crea un objeto que use los códigos de tecla como su clave y la nota a tocar como el valor de dicha clave (empezando en la línea 15):

var keyboard = {
	/* ~ */
	192: 'C,-2',
	/* 1 */
	49: 'C#,-2',
	/* 2 */
	50: 'D,-2',
	/* 3 */
	51: 'D#,-2',
    //...y el resto de las teclas
}

Los comentarios denotan las teclas que el usuario podría presionar en el teclado del computador. Si un usuario presiona la tecla virgulilla, el código de teclado correspondiente es 192. Puedes obtener los códigos de tecla usando herramientas como keycode.info.

El valor de la clave es la nota a tocar que está escrita en el formato 'nota, modificador de octava' donde el modificador de octava representa la posición de octava relativa en relación con la octava que contiene a C central. Por ejemplo, 'C, -2' es la nota C 2 octavas más abajo del C central.

Ten en cuenta que no hay teclas 'bemol'. Cada nota es representada por un 'sostenido'.

Para hacer nuestro teclado de piano funcional, tenemos que preparar una tabla de búsqueda en reversa donde cambiamos los pares clave: valor de tal manera que la nota a tocar se convierta la clave y el código de tecla se convierta en el valor.

Necesitamos dicha tabla porque queremos que itere sobre las notas musicales para generar el teclado fácilmente.

Aquí es donde las cosas podrían volverse complicadas: en realidad necesitamos 2 tablas de búsqueda en reversa.

Usamos una para buscar la etiqueta que queremos mostrar para la tecla del computador que presionamos al tocar una nota (declarada como reverseLookupText en la línea 164) y la segunda para buscar la tecla que fue presionada (declarada como reverseLookup en la línea 165).

Los astutos se darán cuenta de que ambas tablas de búsqueda tienen códigos de tecla como valores, entonces ¿cuál es la diferencia entre estas?

Resulta que (por razones que desconozco) cuando obtienes un código de tecla que corresponde a una tecla y tratas de usar el método String.fromCharCode() en ese código de tecla, no siempre te devuelve la misma cadena de texto que representa la tecla presionada.

Por ejemplo, presionar el corchete izquierdo abierto resulta en el código de tecla 219, pero cuando tratas de convertir el código a una cadena de texto usando String.fromCharCode(219) te devuelve "Û". Para obtener "[", debes usar el código de tecla 91. Reemplazamos los códigos incorrectos empezando desde la línea 168.

Inicialmente, obtener el código de tecla correcto implicó un poco de ensayo y error, pero luego me di cuenta de que simplemente se puede usar otra función (getDispStr() en la línea 318) para forzar que se muestre la cadena de texto correcta.

La mayoría de las teclas se comportan correctamente, pero puedes escoger empezar con un teclado más pequeño para que no tengas que lidiar con códigos de tecla incorrectos.

3. Genera el teclado

Comenzamos el proceso de generación del teclado seleccionando nuestro elemento <div> contenedor del teclado con document.getElementById('keyboard') en la línea 209.

En la siguiente linea, declaramos el objeto selectSound y ponemos la propiedad value en cero para que audioSynth.js cargue el perfil de sonido para piano. Tu podrías querer ingresar un valor diferente (puede ser 0-3) si quieres probar otros instrumentos. Observa la línea 233 de audioSynth.js con Synth.loadSoundProfile para más detalles.

En la línea 216 con var notes, nos traemos las notas disponibles de una octava (C, C#, D...B) desde audioSynth.js.

Generamos nuestro teclado con el bucle que pasa por cada octava y luego por cada nota en dicha octava. Para cada nota, creamos un elemento <div> que represente la tecla adecuada usando document.createElement('div').

Para distinguir si necesitamos crear una tecla negra o blanca, echamos un vistazo al largo del nombre de la nota. Agregar un signo de sostenido hace que el largo del nombre de la nota sea mayor que uno (ex. ‘C#’) lo que indica una tecla negra y viceversa para teclas blancas.

Para cada tecla podemos establecer un ancho, alto y un offset desde la izquierda basada en la posición de la tecla. También podemos crear clases apropiadas para usar con CSS más adelante.

Siguiente, usamos label para etiquetar la tecla con la tecla del computador que necesitamos presionar para tocar la nota y la guardamos en otro elemento <div>. Aquí es donde reverseLookupText es de ayuda. Dentro del mismo <div>, también mostramos el nombre de la nota. Logramos todo esto al colocar la propiedad de label, innerHTML y añadiendo el label a la tecla (líneas 240-242).

label.innerHTML = '<b class="keyLabel">' + s + '</b>' + '<br /><br />' + n.substr(0,1) + 
'<span name="OCTAVE_LABEL" value="' + i + '">' + (__octave + parseInt(i)) + '</span>' + 
(n.substr(1,1)?n.substr(1,1):'');

Similarmente, agregamos un listener de evento a la tecla para manejar los clicks del ratón (line 244):

thisKey.addEventListener(evtListener[0], (function(_temp) { return function() { fnPlayKeyboard({keyCode:_temp}); } })(reverseLookup[n + ',' + i]));

El primer parámetro evtListener[0] es un evento mousedown declarado mucho más atrás en la línea 7. El segundo parámetro es una función que devuelve una función. Necesitamos reverseLookup para obtener el código de tecla correcto y pasar ese valor como parámetro _temp a la función de adentro. No necesitaremos reverseLookup para manejar los eventos keydown.

Este código es pre-ES2015 (o ES6) y el actualizado, que espero sea el equivalente más claro, es el siguiente:

const keyCode = reverseLookup[n + ',' + i];
thisKey.addEventListener('mousedown', () => {
  fnPlayKeyboard({ keyCode });
});

Después de crear y añadir todas las teclas necesarias a nuestro teclado, necesitaremos manejar el hecho de tocar una nota.

4. Maneja presiones de teclas

Manejamos el presionar de las teclas en la misma manera en que el usuario da click en la tecla o presiona la tecla correspondiente del computador a través de la función fnPlayKeyboard en la línea 260. La única diferencia es el tipo de evento que usamos en addEventListener para detectar el presionado de la tecla.

Creamos un arreglo llamado keysPressed en la línea 206 para detectar qué teclas se están presionando o se les está haciendo click. Para simplificar, asumiremos que el presionar de una tecla incluye también que a esta se le haga click.

Podemos dividir el proceso de manejar el presionado de las teclas en 3 pasos: agregar el código de tecla de la tecla presionada en keysPressed, tocar la nota apropiada y quitar el código de tecla de keysPressed.

El primer paso de agregar un código de tecla es fácil:

keysPressed.push(e.keyCode);

donde e es el evento detectado por addEventListener.

Si el código de tecla agregado es uno de los atajos de teclado que asignamos, entonces llamamos a fnPlayNote() en la línea 304 para tocar la nota asociada con esa clave.

En fnPlayNote(), primero creamos un nuevo elemento container de Audio() para nuestra nota usando el método generate() de audiosynth.js. Cuando el audio carga, entonces podremos tocar la nota.

Las líneas 308-313 son código heredado y al parecer pueden simplemente ser reemplazadas por container.play(), sin embargo no he hecho pruebas exhaustivas para ver cuál es la diferencia.

Quitar el presionado de una tecla también es simple, dado que puedes quitar la tecla del arreglo keysPressed con el método splice en la línea 298. Para más detalle, observa la función llamada fnRemoveKeyBinding().

Lo único a lo que tenemos que estar atentos es cuando el usuario mantiene presionada la tecla o presiona varias. Tenemos que asegurarnos que la nota solo se toque una sola vez mientras se mantiene presionada (líneas 262-267):

var i = keysPressed.length;
while(i--) {
	if(keysPressed[i]==e.keyCode) {
		return false;	
    }
}

Devolver false evita que el resto de fnPlayKeyboard() se ejecute.

Resumen

¡Hemos creado un teclado de piano completamente funcional usando Vanilla JavaScript!

Para recapitular, aquí están los pasos que tomamos:

  1. Configuramos nuestro archivo HTML para que cargue los archivos apropiados de JS y ejecute playKeyboard() en <body> para generar y hacer que el teclado sea funcional. Tenemos un elemento <div> con id= "keyboard" donde se mostrará el teclado en la página.
  2. En nuestro archivo de JavaScript playKeyboard.js, asignamos nuestros atajos de teclado con códigos de tecla como clave y las notas musicales como valor. También creamos dos tablas de búsqueda en reversa en donde una es responsable de buscar el label de tecla apropiado basado en la nota y el otro de buscar el código de tecla correcto.
  3. Generamos el teclado dinámicamente con un bucle que pasa por cada nota en el rango de cada octava. Cada tecla es creada como un propio elemento <div>. Usamos las tablas de búsqueda en reversa para generar el label y el código de tecla correcto de cada tecla. Luego un eventListener de mousedown lo usa para llamar a fnPlayKeyboard() para que toque la nota. El evento keydown llama a la misma función, pero no necesita una tabla de búsqueda en reversa para obtener el código de tecla.
  4. Manejamos el presionado de las teclas resultantes de clics del ratón o presionando el teclado del computador en 3 pasos: agregamos el código de tecla a un arreglo, tocamos la nota apropiada y quitamos el código de tecla de dicho arreglo. Debemos tener cuidado de no tocar una nota repetidamente (desde el inicio) mientras el usuario mantiene alguna tecla presionada continuamente.

El teclado ahora es completamente funcional, pero puede que se vea un poco aburrido. ¿Te dejo la parte de CSS a tí?

De nuevo, aquí está el teclado de piano de JavaScript que hice para tu referencia.

Si quieres aprender más sobre Desarrollo Web y ver otros proyectos prolijos, visita mi blog en 1000 Mile World.

¡Gracias por leer y feliz programación!

Traducido del artículo How to Build a Piano Keyboard Using Vanilla JavaScript de Joe Liang