Cómo construir un teclado de piano usando Vanilla JavaScript

Hacer un teclado de piano que se pueda tocar puede ser una excelente manera de aprender un lenguaje de programación (además de ser muy divertido). Este tutorial le muestra cómo codificar uno usando JavaScript vanilla sin la necesidad de bibliotecas o marcos externos.

Aquí está el teclado de piano JavaScript que hice si primero desea ver el producto final.

Este tutorial asume que tiene un conocimiento básico de JavaScript, como funciones y manejo de eventos, así como familiaridad con HTML y CSS. De lo contrario, es totalmente amigable para principiantes y está dirigido a aquellos que desean mejorar sus habilidades de JavaScript a través del aprendizaje basado en proyectos (¡o simplemente quieren hacer un proyecto genial!).

El teclado de piano que estamos haciendo para este proyecto se basa en el teclado sintético generado dinámicamente hecho por Keith William Horwood. Ampliaremos el número de teclas disponibles a 4 octavas y estableceremos nuevas combinaciones de teclas.

Aunque su teclado puede tocar sonidos de otros instrumentos, mantendremos las cosas simples y nos quedaremos con el piano.

Estos son los pasos que seguiremos para abordar este proyecto:

1. Obtenga archivos de trabajo

2. Configurar combinaciones de teclas

3. Generar teclado

4. Manejar pulsaciones de teclas

¡Empecemos!

1. Obtenga archivos de trabajo

Este tutorial utilizará los siguientes archivos:

· Audiosynth.js

· PlayKeyboard.js

Como se mencionó, basaremos nuestro teclado de piano en el hecho por Keith. Naturalmente, también tomaremos prestado parte de su código que amablemente nos ha dado permiso con audiosynth.js.

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

Dejamos intacto el archivo audiosynth.js ya que es el único responsable de la generación de sonido.

El código de este archivo distingue este teclado de piano de otros que se encuentran en línea mediante el uso de Javascript para generar dinámicamente el sonido apropiado cuando el usuario presiona una tecla. Por lo tanto, el código no tiene que cargar ningún archivo de audio externo.

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

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

Cree un archivo HTML de índice y vinculemos a los archivos JS en el encabezado:

En el cuerpo, podemos crear un elemento vacío para que sirva como nuestro "contenedor" de teclado:

Le damos un nombre de identificación para que podamos hacer referencia a él más tarde cuando creemos el teclado usando JS. Podemos ejecutar nuestro código JS llamándolo en el cuerpo también:

playKeyboard()

Usamos playKeyboard.js como una gran función. Se ejecutará tan pronto como el navegador llegue a esa línea de código y generará un teclado completamente funcional en el elemento con

id = “keyboard”.

Las primeras líneas de playKeyboard.js se configuran para la funcionalidad del dispositivo móvil (opcional) y crean un nuevo AudioSynth()objeto. Usamos este objeto para llamar a los métodos de audiosynth.js a los que vinculamos anteriormente. Usamos uno de estos métodos al principio para establecer un volumen para el sonido.

En la línea 11, establecemos la posición del Do central en la cuarta octava.

2. Configurar combinaciones de teclas

Antes de generar el teclado, debemos configurar nuestras combinaciones de teclas, ya que determinan cuántas teclas deben generarse.

Originalmente quería intentar tocar las notas iniciales de 'Für Elise', así que elegí un rango de 4 octavas para un total de 48 teclas en blanco y negro. Esto requirió casi todas las teclas de mi teclado (PC) y puede incluir menos.

Una nota de advertencia: no tengo las mejores combinaciones de teclas, por lo que pueden parecer poco intuitivas cuando intentas jugar. Quizás este sea el precio de intentar crear un teclado de 4 octavas.

Para configurar las combinaciones de teclas, primero cree un objeto que utilizará el código clave como sus claves y la nota que se reproducirá como sus valores clave (línea inicial 15):

var keyboard = { /* ~ */ 192: 'C,-2', /* 1 */ 49: 'C#,-2', /* 2 */ 50: 'D,-2', /* 3 */ 51: 'D#,-2', //...and the rest of the keys } 

Los comentarios denotan las teclas que un usuario puede presionar en un teclado de computadora. Si un usuario presiona la tecla de tilde, entonces el código clave correspondiente es 192. Puede obtener el código clave usando una herramienta como keycode.info.

El valor de la clave es la nota que se tocará y se escribirá en el formato de 'nota, modificador de octava' donde el modificador de octava representa la posición relativa de la octava desde la octava que contiene el Do central. Por ejemplo, 'C, -2' es la nota C 2 octavas por debajo del Do central.

Tenga en cuenta que no hay teclas "planas". Cada nota está representada por un 'sostenido'.

Para que nuestro teclado de piano sea funcional, tenemos que preparar una tabla de búsqueda inversa donde intercambiamos los key: valuepares de modo que la nota a tocar se convierta en la tecla y el código de tecla se convierta en el valor.

Necesitamos una tabla de este tipo porque queremos iterar sobre las notas musicales para generar fácilmente nuestro teclado.

Aquí es donde las cosas pueden complicarse: en realidad necesitamos 2 tablas de búsqueda inversa.

Usamos una tabla para buscar la etiqueta que queremos mostrar para la tecla de la computadora que presionamos para tocar una nota (declarada como reverseLookupTexten la línea 164) y una segunda para buscar la tecla real que se presionó (declarada como reverseLookupen la línea 165).

El astuto puede darse cuenta de que ambas tablas de búsqueda tienen códigos clave como valores, entonces, ¿cuál es la diferencia entre ellos?

It turns out that (for reasons unknown to me) when you get a keycode that corresponds to a key and you try to use String.fromCharCode() method on that keycode, you don’t always get back the same string representing the pressed key.

For example, pressing left open bracket yields keycode 219 but when you actually try to convert the keycode back to a string using String.fromCharCode(219) it returns "Û". To get "[", you have to use key code 91. We replace the incorrect codes starting on line 168.

Getting the right keycode initially involved a bit of trial and error, but later I realized you can just use another function (getDispStr() on line 318) to force the correct string to be displayed.

The majority of the keys do behave properly but you can choose to start with a smaller keyboard so you don’t have to deal with incorrect keycodes.

3. Generate Keyboard

We start the keyboard generation process by selecting our element keyboard container with document.getElementById(‘keyboard’) on line 209.

On the next line, we declare the selectSound object and set the value property to zero to have audioSynth.js load the sound profile for piano. You may wish to enter a different value (can be 0-3) if you want to try out other instruments. See line 233 of audioSynth.js with Synth.loadSoundProfile for more details.

On line 216 with var notes, we retrieve the available notes for one octave (C, C#, D…B) from audioSynth.js.

We generate our keyboard by looping through each octave and then each note in that octave. For each note, we create a element to represent the appropriate key using document.createElement(‘div’).

To distinguish whether we need to create a black or white key, we look at the length of the note name. Adding a sharp sign makes the length of the string greater than one (ex. ‘C#’) which indicates a black key and vice versa for white.

For each key we can set a width, height, and an offset from the left based on key position. We can also set appropriate classes for use with CSS later.

Next, we label the key with the computer key we need to press to play its note and store it in another element. This is where reverseLookupText comes in handy. Inside the same , we also display the note name. We accomplish all of this by setting the label’s innerHTML property and appending the label to the key (lines 240-242).

label.innerHTML = '' + s + '' + '

' + n.substr(0,1) + '' + (__octave + parseInt(i)) + '' + (n.substr(1,1)?n.substr(1,1):'');

Similarly, we add an event listener to the key to handle mouse clicks (line 244):

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

The first parameter evtListener[0] is a mousedown event declared much earlier on line 7. The second parameter is a function that returns a function. We need reverseLookup to get us the correct keycode and we pass that value as a parameter _temp to the inner function. We will not need reverseLookup to handle actual keydown events.

This code is pre-ES2015 (aka ES6) and the updated, hopefully clearer equivalent is:

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

After creating and appending all necessary keys to our keyboard, we will need to handle the actual playing of a note.

4. Handle Key Presses

We handle key presses the same way whether the user clicks the key or presses the corresponding computer key through use of the function fnPlayKeyboard on line 260. The only difference is the type of event we use in addEventListener to detect the key press.

We set up an array called keysPressed in line 206 to detect what keys are being pressed/clicked. For simplicity, we will assume that a key being pressed can include it being clicked as well.

We can divide the process of handling key presses into 3 steps: adding the keycode of the pressed key to keysPressed, playing the appropriate note, and removing the keycode from keysPressed.

The first step of adding a keycode is easy:

keysPressed.push(e.keyCode);

where e is the event detected by addEventListener.

If the added keycode is one of the key bindings we assigned, then we call fnPlayNote() on line 304 to play the note associated with that key.

In fnPlayNote(), we first create a new Audio() element container for our note using the generate() method from audiosynth.js. When the audio loads, we can then play the note.

Lines 308-313 are legacy code and seem they can just be replaced by container.play(), though I have not done any extensive testing to see what the difference is.

Removing a key press is also quite straightforward, as you can just remove the key from the keysPressed array with the splice method on line 298. For more details, see the function called fnRemoveKeyBinding().

The only thing we have to watch out for is when the user holds down a key or multiple keys. We have to make sure that the note only plays once while a key is held down (lines 262-267):

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

Returning false prevents the rest of fnPlayKeyboard() from executing.

Summary

We have created a fully functioning piano keyboard using vanilla JavaScript!

To recap, here are the steps we took:

  1. We set up our index HTML file to load the appropriate JS files and execute

    playKeyboard() in to generate and make the keyboard functional. We have a element with id= "keyboard" where the keyboard will be displayed on the page.

  2. In our JavaScript file playKeyboard.js, we set up our key bindings with keycodes as keys and musical notes as values. We also create two reverse lookup tables in which one is responsible for looking up the appropriate key label based on the note and the other for looking up the correct keycode.

  3. We dynamically generate the keyboard by looping through every note in each octave range. Each key is created as its own element. We use the reverse lookup tables to generate the key label and correct keycode. Then an event listener on mousedown uses it to call fnPlayKeyboard() to play the note. The

    keydown event calls the same function but does not need a reverse lookup table to get the keycode.

  4. We handle key presses resulting from either mouse clicks or computer key presses in 3 steps: add keycode of the pressed key to an array, play the appropriate note, and remove keycode from that array. We must be careful not to repeatedly play a note (from the beginning) while the user continuously holds down a key.

The keyboard is now fully functional but it may look a bit dull. I will leave the CSS part to you ?

Again, here is the JavaScript piano keyboard I made for reference.

If you want to learn more about web development and check out some other neat projects, visit my blog at 1000 Mile World.

Thanks for reading and happy coding!