¿Un tutorial sobre Web MIDI? ¿En el año 2016? ¿Estás de broma, verdad? ¡No! ¡No es lo que piensas! Para aquellos que ya navegaban en la década de los noventa, el término "Web MIDI" generalmente induce recuerdos de una época en que los sitios web reproducían automáticamente una versión bloopy lo-fi de The Final Countdown, y mientras se firmaba el libro de los webmasters. Sin embargo, en 2016, el Web-MIDI, y en concreto la Web MIDI-API, cuenta aún con mucho potencial.
Para el que no lo sepa, las siglas MIDI aluden a Musical Instrument Digital Interface. Es un protocolo que permite a los instrumentos musicales electrónicos, ordenadores y otros dispositivos, comunicarse entre sí. Funciona mediante el envío de mensajes pequeños de un dispositivo a otro diciéndose cosas del estilo a "la nota 12 acaba de ser presionada" o "la nota 62 no ha sido pulsada más", pero en taquigrafía digital.
La Web MIDI API utiliza este protocolo y te permite tener un instrumento MIDI habilitado, como un teclado, conectarlo a tu ordenador y obtener la información enviada desde el teclado a tu navegador.
Actualmente, la Web MIDI API sólo es soportada en Chrome y Opera, pero puedes seguir su progreso en Firefox visitando este bug.
Así que, ¿para qué queremos conectar un teclado a un navegador web? Bueno, no muchos músicos conocen los teclados QWERTY tanto como conocen los musicales. Además, la gama de dispositivos musicales que soporta MIDI es enorme. Al conectar un dispositivo de entrada MIDI a nuestro navegador, utilizando la Web MIDI API, somos capaces de crear instrumentos musicales en la web.
¿Quieres tocar el piano? Sólo tienes que conectar el teclado y visitar una página web que utilice esta tecnología para reproducir el sonido de un piano. ¿Quieres un sonido diferente? Sólo tienes que visitar un sitio diferente.
Esperamos que puedas ver las ventajas de esta API, pero ¿cómo funciona realmente?
Acceso al dispositivo MIDI
En primer lugar queremos comprobar si nuestro navegador es compatible con la Web MIDI API. Hacemos esto chequeando si existe el método navigator.requestMIDIAccess. Este método sólo se implementa en los navegadores que soportan la API.
if (navigator.requestMIDIAccess) { console.log('Browser supports MIDI!'); }
Ahora que sabemos que existe el método, vamos a llamarlo para solicitar el acceso a cualquier entrada MIDI que venga desde el navegador.
if (navigator.requestMIDIAccess) { navigator.requestMIDIAccess() .then(success, failure); }
navigator.requestMIDIAccess() devuelve una promesa, lo que significa que o bien llama a una función éxito o una función de fallo en función del resultado de la solicitud al acceso MIDI. Aquí le hemos dado el nombre de dos funciones que vamos a crear próximamente.
function success (midi) { console.log('Got midi!', midi); } function failure () { console.error('No access to your midi devices.') }
Como puedes ver, nuestra función de success toma un parámetro MIDI en forma de objeto MIDIAccess. El objeto MIDIAccess es clave para recibir datos MIDI. El objeto en sí mismo proporciona una interfaz para los dispositivos MIDI que haya conectados. Las entradas representan los dispositivos MIDI que haya conectados a tu ordenador. Tengo un solo teclado MIDI conectado, por lo que si tuviera que comprobar el midi.inputs.size, retornaría "1".
Con el fin de obtener los datos de entrada de nuestro dispositivo, primero creamos una variable y le asignamos el midi.inputs.values () de esta manera.
var inputs = midi.inputs.values();
Una cosa importante a tener en cuenta es que el valor asignado a inputs es un iterador. Un iterador es un objeto que sabe cómo acceder a sus propiedades de una en una. Cuenta con un método next() que te permite obtener el siguiente elemento de la secuencia. También cuenta con la propiedad done para saber si ya hemos iterado sobre todas las propiedades del objeto. Esto significa que podemos escribir bucles de esta manera:
for (var input = inputs.next(); input && !input.done; input = inputs.next()) { // each time there is a midi message call the onMIDIMessage function input.value.onmidimessage = onMIDIMessage; }
Lo que este bucle está diciendo es:
- Creamos una variable llamada input y asignamos el próximo input a él. Debido a que no iteramos sobre todos los inputs aun, esto devolverá el primero de los inputs.
- Si tenemos un input y el valor done del iterador de inputs no es igual a true, seguimos con el bucle
- Definimos input al siguiente input con el objeto iterador.
También te habrás dado cuenta de que dentro de este bucle for, le asignamos una función al listener onmidimessage del input. Esta función es llamada cada vez que un evento MIDI es recibido desde el dispositivo representado por ese input. Vamos a crear esa función:
function onMIDIMessage (message) { console.log(message.data); }
Decodificando datos MIDI
La parte del mensaje MIDI que nos interesa son sus datos; qué tipo de evento MIDI han sido enviados? ¿Qué tecla del teclado se ha pulsado?
Si estás siguiendo este tutorial, verá que cuando se pulsa una tecla en el teclado, el navegador logueará algo como [144, 61, 95] en la consola. Y cuando quitas el dedo de la tecla, el navegador de nuevo registrará algo ligeramente diferente, como [128, 61, 0].
Esta matriz se puede analizar como tal. El primer elemento es el tipo de evento MIDI. Los mensajes MIDI pueden contener un número bastante reducido de eventos, y cada evento tiene un número correspondiente. En nuestro caso, el 144 asigna un mensaje NoteOn, lo que indica que una tecla ha sido presionada, mientras que el 128 es un mensaje NoteOff, que nos dice ya no se está presionando una tecla. Para obtener una lista completa de los posibles tipos de eventos MIDI, echa un vistazo a la lista de mensajes del protocolo MIDI.
El segundo valor de la matriz representa qué tecla del teclado ha sido presionada. Cada nota en un teclado se le asigna un número del 0 al 127. En el ejemplo anterior, he pulsado la tecla 61, que mediante el uso de esta tabla de consulta podemos ver era un C #.
El tercer y último valor de la matriz representa la velocidad, básicamente la velocidad en qué la tecla ha sido pulsada. Esto puede ser usado para imitar un piano donde las teclas se pueden reproducir más bajas si las pulsas poco o viceversa.
Ahora que sabemos qué número de tecla está siendo presionada o liberada, vamos a convertir esto en algo útil. Conectamos la API Web MIDI a la Web Audio API.
Creando un instrumento web
Vamos a convertir nuestro navegador en un mini sintetizador. Vamos a crear un oscilador que genere la frecuencia de la nota pulsada, por lo que vamos a necesitar convertir el número de nota MIDI a su frecuencia correspondiente. Afortunadamente, nuestra buena amiga Wikipedia nos da un poco de información sobre cómo llevar a cabo precisamente esto. Así es cómo se vería en Javascript:
function midiNoteToFrequency (note) { return Math.pow(2, ((note - 69) / 12)) * 440; }
Proporciona una nota y obtén la frecuencia de vuelta. Vamos a usar esto en nuestra función onMIDIMessage.
function onMIDIMessage (message) { var frequency = midiNoteToFrequency(message.data[1]); }
Ahora vamos a tocar una nota de esta frecuencia si el mensaje MIDI es un mensaje NoteOn
if (message.data[0] === 144 && message.data[2] > 0) { playNote(frequency); }
Probablemente entiendas la primera parte de esta sentencia if con bastante facilidad. Estamos comprobando que el tipo de mensaje es 144, que es el mensaje NoteOn.
Pero ¿qué pasa con la segunda parte? Bueno, algunos dispositivos MIDI, en lugar de enviar un mensaje NoteOff, enviarán un mensaje NoteOn con velocidad cero, por lo que estamos comprobando que el mensaje tenga una velocidad mayor que cero.
Ahora que hemos cubierto NoteOn, escribiremos algo similar para NoteOff. El valor del mensaje de NoteOff es 128, por lo que necesitamos comprobarlo no sólo para ese valor, sino también si su velocidad es cero con el fin de cubrir la situación que acabo de mencionar.
if (message.data[0] === 128 || message.data[2] === 0) { stopNote(frequency); }
Todo lo que tenemos que hacer ahora es completar las funciones startNote y stopNote. Esto es trabajo de la Web Audio API, y por lo tanto está tristemente fuera del alcance de este tutorial, pero si conoces la API, el siguiente código te resultará muy familiar.
var context = new AudioContext(), oscillators = {}; if (navigator.requestMIDIAccess) { navigator.requestMIDIAccess() .then(success, failure); } function success (midi) { var inputs = midi.inputs.values(); // inputs is an Iterator for (var input = inputs.next(); input && !input.done; input = inputs.next()) { // each time there is a midi message call the onMIDIMessage function input.value.onmidimessage = onMIDIMessage; } } function failure () { console.error('No access to your midi devices.') } function onMIDIMessage (message) { var frequency = midiNoteToFrequency(message.data[1]); if (message.data[0] === 144 && message.data[2] > 0) { playNote(frequency); } if (message.data[0] === 128 || message.data[2] === 0) { stopNote(frequency); } } function midiNoteToFrequency (note) { return Math.pow(2, ((note - 69) / 12)) * 440; } function playNote (frequency) { oscillators[frequency] = context.createOscillator(); oscillators[frequency].frequency.value = frequency; oscillators[frequency].connect(context.destination); oscillators[frequency].start(context.currentTime); } function stopNote (frequency) { oscillators[frequency].stop(context.currentTime); oscillators[frequency].disconnect(); }