Tutorial MIDI: creación de aplicaciones de audio basadas en navegador controladas por hardware MIDI
Publicado: 2022-03-11Si bien la Web Audio API está ganando popularidad, especialmente entre los desarrolladores de juegos HTML5, la Web MIDI API aún es poco conocida entre los desarrolladores frontend. Una gran parte de esto probablemente tenga que ver con su actual falta de soporte y documentación accesible; Actualmente, la API Web MIDI solo es compatible con Google Chrome, siempre que habilite una marca especial para ella. Los fabricantes de navegadores actualmente ponen poco énfasis en esta API, ya que se planea que sea parte del estándar ES7.
Diseñado a principios de los años 80 por varios representantes de la industria de la música, MIDI (abreviatura de Musical Instrument Digital Interface) es un protocolo de comunicación estándar para dispositivos de música electrónica. Aunque desde entonces se han desarrollado otros protocolos, como OSC; Treinta años después, MIDI sigue siendo el protocolo de comunicación de facto para los fabricantes de hardware de audio. Será difícil encontrar un productor de música moderno que no posea al menos un dispositivo MIDI en su estudio.
Con el rápido desarrollo y la adopción de Web Audio API, ahora podemos comenzar a crear aplicaciones basadas en navegador que cierran la brecha entre la nube y el mundo físico. La Web MIDI API no solo nos permite construir sintetizadores y efectos de audio, sino que incluso podemos comenzar a construir una DAW (estación de trabajo de audio digital) basada en navegador similar en características y rendimiento a sus contrapartes actuales basadas en flash (consulte Audiotool, por ejemplo). ).
En este tutorial de MIDI, lo guiaré a través de los conceptos básicos de la Web MIDI API y construiremos un monosintetizador simple que podrá tocar con su dispositivo MIDI favorito. El código fuente completo está disponible aquí y puede probar la demostración en vivo directamente. Si no posee un dispositivo MIDI, aún puede seguir este tutorial consultando la rama 'teclado' del repositorio de GitHub, que habilita el soporte básico para el teclado de su computadora, para que pueda tocar notas y cambiar octavas. Esta es también la versión que está disponible como demostración en vivo. Sin embargo, debido a las limitaciones del hardware de la computadora, la velocidad y la desafinación se desactivan cada vez que usa el teclado de su computadora para controlar el sintetizador. Consulte el archivo Léame en GitHub para obtener información sobre la asignación de teclas/notas.
Requisitos previos del tutorial Midi
Necesitará lo siguiente para este tutorial MIDI:
- Google Chrome (versión 38 o superior) con el indicador
#enable-web-midi
habilitado - (Opcionalmente) Un dispositivo MIDI, que puede disparar notas, conectado a su computadora
También usaremos Angular.js para darle un poco de estructura a nuestra aplicación; por lo tanto, el conocimiento básico del marco es un requisito previo.
Empezando
Modularizaremos nuestra aplicación MIDI desde cero separándola en 3 módulos:
- WebMIDI: manejo de los distintos dispositivos MIDI conectados a su computadora
- WebAudio: proporcionando la fuente de audio para nuestro sintetizador
- WebSynth: conectando la interfaz web al motor de audio
Un módulo de App
manejará la interacción del usuario con la interfaz de usuario web. La estructura de nuestra aplicación podría parecerse un poco a esto:
|- app |-- js |--- midi.js |--- audio.js |--- synth.js |--- app.js |- index.html
También debe instalar las siguientes bibliotecas para ayudarlo a desarrollar su aplicación: Angular.js, Bootstrap y jQuery. Probablemente la forma más fácil de instalarlos sea a través de Bower.
El Módulo WebMIDI: Conexión con el Mundo Real
Comencemos a descubrir cómo usar MIDI conectando nuestros dispositivos MIDI a nuestra aplicación. Para hacerlo, crearemos una fábrica simple que devuelva un solo método. Para conectarnos a nuestros dispositivos MIDI a través de la Web MIDI API, debemos llamar al método navigator.requestMIDIAccess
:
angular .module('WebMIDI', []) .factory('Devices', ['$window', function($window) { function _connect() { if($window.navigator && 'function' === typeof $window.navigator.requestMIDIAccess) { $window.navigator.requestMIDIAccess(); } else { throw 'No Web MIDI support'; } } return { connect: _connect }; }]);
¡Y eso es todo!
El método requestMIDIAccess
devuelve una promesa, por lo que podemos devolverla directamente y manejar el resultado de la promesa en el controlador de nuestra aplicación:
angular .module('DemoApp', ['WebMIDI']) .controller('AppCtrl', ['$scope', 'Devices', function($scope, devices) { $scope.devices = []; devices .connect() .then(function(access) { if('function' === typeof access.inputs) { // deprecated $scope.devices = access.inputs(); console.error('Update your Chrome version!'); } else { if(access.inputs && access.inputs.size > 0) { var inputs = access.inputs.values(), input = null; // iterate through the devices for (input = inputs.next(); input && !input.done; input = inputs.next()) { $scope.devices.push(input.value); } } else { console.error('No devices detected!'); } } }) .catch(function(e) { console.error(e); }); }]);
Como se mencionó, el método requestMIDIAccess
devuelve una promesa, pasando un objeto al método then
, con dos propiedades: entradas y salidas.
En versiones anteriores de Chrome, estas dos propiedades eran métodos que le permitían recuperar una matriz de dispositivos de entrada y salida directamente. Sin embargo, en las últimas actualizaciones, estas propiedades ahora son objetos. Esto hace una gran diferencia, ya que ahora necesitamos llamar al método de values
en el objeto de entradas o salidas para recuperar la lista de dispositivos correspondiente. Este método actúa como una función generadora y devuelve un iterador. Nuevamente, esta API está destinada a ser parte de ES7; por lo tanto, tiene sentido implementar un comportamiento similar al de un generador, aunque no es tan sencillo como la implementación original.
Finalmente, podemos recuperar la cantidad de dispositivos a través de la propiedad de size
del objeto iterador. Si hay al menos un dispositivo, simplemente iteramos sobre el resultado llamando al next
método del objeto iterador y empujando cada dispositivo a una matriz definida en $scope. En el front-end, podemos implementar un cuadro de selección simple que enumerará todos los dispositivos de entrada disponibles y nos permitirá elegir qué dispositivo queremos usar como dispositivo activo para controlar el sintetizador web:
<select ng-model="activeDevice" class="form-control" ng-options="device.manufacturer + ' ' + device.name for device in devices"> <option value="" disabled>Choose a MIDI device...</option> </select>
activeDevice
este cuadro de selección a una variable $scope llamada dispositivo activo que luego usaremos para conectar este dispositivo activo al sintetizador.
El módulo WebAudio: hacer ruido
La API de WebAudio nos permite no solo reproducir archivos de sonido, sino también generar sonidos al recrear los componentes esenciales de los sintetizadores, como osciladores, filtros y nodos de ganancia, entre otros.
Crear un oscilador
El papel de los osciladores es generar una forma de onda. Hay varios tipos de formas de onda, entre las cuales cuatro son compatibles con la API de WebAudio: sinusoidal, cuadrada, triangular y de diente de sierra. Se dice que las formas de onda "oscilan" a una determinada frecuencia, pero también es posible definir su propia tabla de ondas personalizada si es necesario. Cierto rango de frecuencias son audibles por los seres humanos - se conocen como sonidos. Alternativamente, cuando oscilan a bajas frecuencias, los osciladores también pueden ayudarnos a construir LFO ("osciladores de baja frecuencia") para que podamos modular nuestros sonidos (pero eso está más allá del alcance de este tutorial).
Lo primero que debemos hacer para crear algún sonido es instanciar un nuevo AudioContext
:
function _createContext() { self.ctx = new $window.AudioContext(); }
A partir de ahí, podemos crear instancias de cualquiera de los componentes que la API de WebAudio pone a disposición. Dado que podemos crear varias instancias de cada componente, tiene sentido crear servicios para poder crear instancias nuevas y únicas de los componentes que necesitamos. Comencemos creando el servicio para generar un nuevo oscilador:
angular .module('WebAudio', []) .service('OSC', function() { var self; function Oscillator(ctx) { self = this; self.osc = ctx.createOscillator(); return self; } });
Ahora podemos instanciar nuevos osciladores a nuestra voluntad, pasando como argumento la instancia de AudioContext que creamos anteriormente. Para facilitar las cosas en el futuro, agregaremos algunos métodos de envoltorio, mera azúcar sintáctica, y devolveremos la función Oscillator:
Oscillator.prototype.setOscType = function(type) { if(type) { self.osc.type = type } } Oscillator.prototype.setFrequency = function(freq, time) { self.osc.frequency.setTargetAtTime(freq, 0, time); }; Oscillator.prototype.start = function(pos) { self.osc.start(pos); } Oscillator.prototype.stop = function(pos) { self.osc.stop(pos); } Oscillator.prototype.connect = function(i) { self.osc.connect(i); } Oscillator.prototype.cancel = function() { self.osc.frequency.cancelScheduledValues(0); } return Oscillator;
Crear un filtro multipaso y un control de volumen
Necesitamos dos componentes más para completar nuestro motor de audio básico: un filtro multipaso, para darle un poco de forma a nuestro sonido, y un nodo de ganancia para controlar el volumen de nuestro sonido y encenderlo y apagarlo. Para hacerlo, podemos proceder de la misma manera que hicimos con el oscilador: crear servicios que devuelvan una función con algunos métodos de envoltura. Todo lo que tenemos que hacer es proporcionar la instancia de AudioContext y llamar al método apropiado.
Creamos un filtro llamando al método createBiquadFilter
de la instancia de AudioContext:
ctx.createBiquadFilter();
De manera similar, para un nodo de ganancia, llamamos al método createGain
:
ctx.createGain();
El módulo WebSynth: conectar las cosas
Ahora estamos casi listos para construir nuestra interfaz de sintetizador y conectar dispositivos MIDI a nuestra fuente de audio. Primero, necesitamos conectar nuestro motor de audio y prepararlo para recibir notas MIDI. Para conectar el motor de audio, simplemente creamos nuevas instancias de los componentes que necesitamos y luego los "conectamos" usando el método de connect
disponible para las instancias de cada componente. El método de connect
toma un argumento, que es simplemente el componente al que desea conectar la instancia actual. Es posible orquestar una cadena de componentes más elaborada, ya que el método de connect
puede conectar un nodo a múltiples moduladores (lo que hace posible implementar cosas como fundido cruzado y más).
self.osc1 = new Oscillator(self.ctx); self.osc1.setOscType('sine'); self.amp = new Amp(self.ctx); self.osc1.connect(self.amp.gain); self.amp.connect(self.ctx.destination); self.amp.setVolume(0.0, 0); //mute the sound self.filter1.disconnect(); self.amp.disconnect(); self.amp.connect(self.ctx.destination); }
Acabamos de construir el cableado interno de nuestro motor de audio. Puedes jugar un poco y probar diferentes combinaciones de cableado, pero recuerda bajar el volumen para no quedarte sordo. Ahora podemos conectar la interfaz MIDI a nuestra aplicación y enviar mensajes MIDI al motor de audio. Configuraremos un observador en el cuadro de selección del dispositivo para "conectarlo" virtualmente a nuestro sintetizador. Luego escucharemos los mensajes MIDI provenientes del dispositivo y pasaremos la información al motor de audio:
// in the app's controller $scope.$watch('activeDevice', DSP.plug); // in the synth module function _onmidimessage(e) { /** * e.data is an array * e.data[0] = on (144) / off (128) / detune (224) * e.data[1] = midi note * e.data[2] = velocity || detune */ switch(e.data[0]) { case 144: Engine.noteOn(e.data[1], e.data[2]); break; case 128: Engine.noteOff(e.data[1]); break; } } function _plug(device) { self.device = device; self.device.onmidimessage = _onmidimessage; }
Aquí, estamos escuchando eventos MIDI desde el dispositivo, analizando los datos del objeto MidiEvent y pasándolos al método apropiado; ya sea noteOn
o noteOff
, según el código de evento (144 para noteOn, 128 para noteOff). Ahora podemos agregar la lógica en los métodos respectivos en el módulo de audio para generar un sonido:
function _noteOn(note, velocity) { self.activeNotes.push(note); self.osc1.cancel(); self.currentFreq = _mtof(note); self.osc1.setFrequency(self.currentFreq, self.settings.portamento); self.amp.cancel(); self.amp.setVolume(1.0, self.settings.attack); } function _noteOff(note) { var position = self.activeNotes.indexOf(note); if (position !== -1) { self.activeNotes.splice(position, 1); } if (self.activeNotes.length === 0) { // shut off the envelope self.amp.cancel(); self.currentFreq = null; self.amp.setVolume(0.0, self.settings.release); } else { // in case another note is pressed, we set that one as the new active note self.osc1.cancel(); self.currentFreq = _mtof(self.activeNotes[self.activeNotes.length - 1]); self.osc1.setFrequency(self.currentFreq, self.settings.portamento); } }
Algunas cosas están sucediendo aquí. En el método noteOn
, primero empujamos la nota actual a una matriz de notas. Aunque estamos construyendo un sintetizador mono (lo que significa que solo podemos tocar una nota a la vez), aún podemos tener varios dedos a la vez en el teclado. Por lo tanto, debemos poner en cola todas estas notas para que cuando soltemos una nota, se reproduzca la siguiente. Luego, debemos detener el oscilador para asignar la nueva frecuencia, que convertimos de una nota MIDI (escala de 0 a 127) a un valor de frecuencia real con un poco de matemática:
function _mtof(note) { return 440 * Math.pow(2, (note - 69) / 12); }
En el método noteOff
, primero comenzamos por encontrar la nota en la matriz de notas activas y eliminarla. Luego, si era la única nota del arreglo, simplemente apagamos el volumen.
El segundo argumento del método setVolume
es el tiempo de transición, es decir, cuánto tarda la ganancia en alcanzar el nuevo valor de volumen. En términos musicales, si la nota está encendida, sería el equivalente al tiempo de ataque, y si la nota está apagada, sería el equivalente al tiempo de liberación.

El Módulo WebAnalyser: Visualizando nuestro Sonido
Otra característica interesante que podemos agregar a nuestro sintetizador es un nodo analizador, que nos permite mostrar la forma de onda de nuestro sonido usando un lienzo para representarlo. Crear un nodo analizador es un poco más complicado que otros objetos AudioContext, ya que también requiere crear un nodo scriptProcessor para realizar el análisis. Comenzamos seleccionando el elemento canvas en el DOM:
function Analyser(canvas) { self = this; self.canvas = angular.element(canvas) || null; self.view = self.canvas[0].getContext('2d') || null; self.javascriptNode = null; self.analyser = null; return self; }
Luego, agregamos un método de connect
, en el que crearemos tanto el analizador como el procesador de scripts:
Analyser.prototype.connect = function(ctx, output) { // setup a javascript node self.javascriptNode = ctx.createScriptProcessor(2048, 1, 1); // connect to destination, else it isn't called self.javascriptNode.connect(ctx.destination); // setup an analyzer self.analyser = ctx.createAnalyser(); self.analyser.smoothingTimeConstant = 0.3; self.analyser.fftSize = 512; // connect the output to the destination for sound output.connect(ctx.destination); // connect the output to the analyser for processing output.connect(self.analyser); self.analyser.connect(self.javascriptNode); // define the colors for the graph var gradient = self.view.createLinearGradient(0, 0, 0, 200); gradient.addColorStop(1, '#000000'); gradient.addColorStop(0.75, '#ff0000'); gradient.addColorStop(0.25, '#ffff00'); gradient.addColorStop(0, '#ffffff'); // when the audio process event is fired on the script processor // we get the frequency data into an array // and pass it to the drawSpectrum method to render it in the canvas self.javascriptNode.onaudioprocess = function() { // get the average for the first channel var array = new Uint8Array(self.analyser.frequencyBinCount); self.analyser.getByteFrequencyData(array); // clear the current state self.view.clearRect(0, 0, 1000, 325); // set the fill style self.view.fillStyle = gradient; drawSpectrum(array); } };
Primero, creamos un objeto scriptProcessor y lo conectamos al destino. Luego, creamos el propio analizador, que alimentamos con la salida de audio del oscilador o filtro. ¡Observe cómo todavía necesitamos conectar la salida de audio al destino para que podamos escucharlo! También necesitamos definir los colores degradados de nuestro gráfico; esto se hace llamando al método createLinearGradient
del elemento canvas.
Finalmente, scriptProcessor disparará un evento de 'audioprocess' en un intervalo; cuando se activa este evento, calculamos las frecuencias promedio capturadas por el analizador, limpiamos el lienzo y volvemos a dibujar el nuevo gráfico de frecuencia llamando al método drawSpectrum
:
function drawSpectrum(array) { for (var i = 0; i < (array.length); i++) { var v = array[i], h = self.canvas.height(); self.view.fillRect(i * 2, h - (v - (h / 4)), 1, v + (h / 4)); } }
Por último, pero no menos importante, necesitaremos modificar un poco el cableado de nuestro motor de audio para acomodar este nuevo componente:
// in the _connectFilter() method if(self.analyser) { self.analyser.connect(self.ctx, self.filter1); } else { self.filter1.connect(self.ctx.destination); } // in the _disconnectFilter() method if(self.analyser) { self.analyser.connect(self.ctx, self.amp); } else { self.amp.connect(self.ctx.destination); }
¡Ahora tenemos un buen visualizador que nos permite mostrar la forma de onda de nuestro sintetizador en tiempo real! Esto implica un poco de trabajo de configuración, pero es muy interesante y perspicaz, especialmente cuando se usan filtros.
Desarrollando nuestro sintetizador: agregando velocidad y desafinación
En este punto de nuestro tutorial MIDI, tenemos un sintetizador bastante bueno, pero toca todas las notas al mismo volumen. Esto se debe a que, en lugar de manejar correctamente los datos de velocidad, simplemente establecemos el volumen en un valor fijo de 1,0. Comencemos arreglando eso y luego veremos cómo podemos habilitar la rueda de desafinación que se encuentra en la mayoría de los teclados MIDI comunes.
Velocidad de habilitación
Si no está familiarizado con él, la 'velocidad' se relaciona con la fuerza con la que presiona la tecla en su teclado. Según este valor, el sonido creado parece más suave o más fuerte.
En nuestro sintetizador tutorial MIDI, podemos emular este comportamiento simplemente jugando con el volumen del nodo de ganancia. Para hacerlo, primero debemos hacer un poco de matemática para convertir los datos MIDI en un valor flotante entre 0.0 y 1.0 para pasar al nodo de ganancia:
function _vtov (velocity) { return (velocity / 127).toFixed(2); }
El rango de velocidad de un dispositivo MIDI es de 0 a 127, por lo que simplemente dividimos ese valor por 127 y devolvemos un valor flotante con dos decimales. Luego, podemos actualizar el método _noteOn
para pasar el valor calculado al nodo de ganancia:
self.amp.setVolume(_vtov(velocity), self.settings.attack);
¡Y eso es! Ahora, cuando toquemos nuestro sintetizador, notaremos que los volúmenes varían según la fuerza con la que presionemos las teclas de nuestro teclado.
Habilitación de la rueda de desafinación en su teclado MIDI
La mayoría de los teclados MIDI cuentan con una rueda de desafinación; la rueda le permite alterar ligeramente la frecuencia de la nota que se está reproduciendo actualmente, creando un efecto interesante conocido como 'desafinación'. Esto es bastante fácil de implementar a medida que aprende a usar MIDI, ya que la rueda de desafinación también dispara un evento MidiMessage con su propio código de evento (224), que podemos escuchar y actuar al recalcular el valor de frecuencia y actualizar el oscilador.
Primero, necesitamos capturar el evento en nuestro sintetizador. Para hacerlo, agregamos un caso adicional a la declaración de cambio que creamos en la devolución de llamada _onmidimessage
:
case 224: // the detune value is the third argument of the MidiEvent.data array Engine.detune(e.data[2]); break;
Luego, definimos el método de detune
en el motor de audio:
function _detune(d) { if(self.currentFreq) { //64 = no detune if(64 === d) { self.osc1.setFrequency(self.currentFreq, self.settings.portamento); self.detuneAmount = 0; } else { var detuneFreq = Math.pow(2, 1 / 12) * (d - 64); self.osc1.setFrequency(self.currentFreq + detuneFreq, self.settings.portamento); self.detuneAmount = detuneFreq; } } }
El valor de desafinación predeterminado es 64, lo que significa que no se aplica ninguna desafinación, por lo que en este caso simplemente pasamos la frecuencia actual al oscilador.
Finalmente, también necesitamos actualizar el método _noteOff
, para tener en cuenta la desafinación en caso de que se ponga en cola otra nota:
self.osc1.setFrequency(self.currentFreq + self.detuneAmount, self.settings.portamento);
Crear la interfaz
Hasta ahora, solo creamos un cuadro de selección para poder seleccionar nuestro dispositivo MIDI y un visualizador de forma de onda, pero no tenemos la posibilidad de modificar el sonido directamente interactuando con la página web. Vamos a crear una interfaz muy simple utilizando elementos de formulario comunes y vincularlos a nuestro motor de audio.
Creación de un diseño para la interfaz
Crearemos varios elementos de forma para controlar el sonido de nuestro sintetizador:
- Un grupo de radio para seleccionar el tipo de oscilador
- Una casilla de verificación para habilitar / deshabilitar el filtro
- Un grupo de radio para seleccionar el tipo de filtro.
- Dos rangos para controlar la frecuencia y la resonancia del filtro.
- Dos rangos para controlar el ataque y la liberación del nodo de ganancia
Al crear un documento HTML para nuestra interfaz, deberíamos terminar con algo como esto:
<div class="synth container" ng-controller="WebSynthCtrl"> <h1>webaudio synth</h1> <div class="form-group"> <select ng-model="activeDevice" class="form-control" ng-options="device.manufacturer + ' ' + device.name for device in devices"> <option value="" disabled>Choose a MIDI device...</option> </select> </div> <div class="col-lg-6 col-md-6 col-sm-6"> <h2>Oscillator</h2> <div class="form-group"> <h3>Oscillator Type</h3> <label ng-repeat="t in oscTypes"> <input type="radio" name="oscType" ng-model="synth.oscType" value="{{t}}" ng-checked="'{{t}}' === synth.oscType" /> {{t}} </label> </div> <h2>Filter</h2> <div class="form-group"> <label> <input type="checkbox" ng-model="synth.filterOn" /> enable filter </label> </div> <div class="form-group"> <h3>Filter Type</h3> <label ng-repeat="t in filterTypes"> <input type="radio" name="filterType" ng-model="synth.filterType" value="{{t}}" ng-disabled="!synth.filterOn" ng-checked="synth.filterOn && '{{t}}' === synth.filterType" /> {{t}} </label> </div> <div class="form-group"> <!-- frequency --> <label>filter frequency:</label> <input type="range" class="form-control" min="50" max="10000" ng-model="synth.filterFreq" ng-disabled="!synth.filterOn" /> </div> <div class="form-group"> <!-- resonance --> <label>filter resonance:</label> <input type="range" class="form-control" min="0" max="150" ng-model="synth.filterRes" ng-disabled="!synth.filterOn" /> </div> </div> <div class="col-lg-6 col-md-6 col-sm-6"> <div class="panel panel-default"> <div class="panel-heading">Analyser</div> <div class="panel-body"> <!-- frequency analyser --> <canvas></canvas> </div> </div> <div class="form-group"> <!-- attack --> <label>attack:</label> <input type="range" class="form-control" min="50" max="2500" ng-model="synth.attack" /> </div> <div class="form-group"> <!-- release --> <label>release:</label> <input type="range" class="form-control" min="50" max="1000" ng-model="synth.release" /> </div> </div> </div>
Decorar la interfaz de usuario para que se vea elegante no es algo que cubriré en este tutorial MIDI básico; en cambio, podemos guardarlo como un ejercicio para luego pulir la interfaz de usuario, tal vez para que se vea así:
Vinculación de la interfaz al motor de audio
Deberíamos definir algunos métodos para vincular estos controles a nuestro motor de audio.
Controlar el oscilador
Para el oscilador, solo necesitamos un método que nos permita establecer el tipo de oscilador:
Oscillator.prototype.setOscType = function(type) { if(type) { self.osc.type = type; } }
Controlar el filtro
Para el filtro necesitamos tres controles: uno para el tipo de filtro, uno para la frecuencia y otro para la resonancia. También podemos conectar los métodos _connectFilter
y _disconnectFilter
al valor de la casilla de verificación.
Filter.prototype.setFilterType = function(type) { if(type) { self.filter.type = type; } } Filter.prototype.setFilterFrequency = function(freq) { if(freq) { self.filter.frequency.value = freq; } } Filter.prototype.setFilterResonance = function(res) { if(res) { self.filter.Q.value = res; } }
Controlar el ataque y la resonancia
Para moldear un poco nuestro sonido, podemos cambiar los parámetros de ataque y liberación del nodo de ganancia. Necesitamos dos métodos para esto:
function _setAttack(a) { if(a) { self.settings.attack = a / 1000; } } function _setRelease(r) { if(r) { self.settings.release = r / 1000; } }
Configuración de observadores
Finalmente, en el controlador de nuestra aplicación, solo necesitamos configurar algunos observadores y vincularlos a los diversos métodos que acabamos de crear:
$scope.$watch('synth.oscType', DSP.setOscType); $scope.$watch('synth.filterOn', DSP.enableFilter); $scope.$watch('synth.filterType', DSP.setFilterType); $scope.$watch('synth.filterFreq', DSP.setFilterFrequency); $scope.$watch('synth.filterRes', DSP.setFilterResonance); $scope.$watch('synth.attack', DSP.setAttack); $scope.$watch('synth.release', DSP.setRelease);
Conclusión
Se cubrieron muchos conceptos en este tutorial MIDI; principalmente, descubrimos cómo usar la API WebMIDI, que está bastante indocumentada aparte de la especificación oficial del W3C. La implementación de Google Chrome es bastante sencilla, aunque el cambio a un objeto iterador para los dispositivos de entrada y salida requiere un poco de refactorización para el código heredado que usa la implementación anterior.
En cuanto a la API de WebAudio, esta es una API muy rica y solo cubrimos algunas de sus capacidades en este tutorial. A diferencia de la API WebMIDI, la API WebAudio está muy bien documentada, en particular en Mozilla Developer Network. La red de desarrolladores de Mozilla contiene una gran cantidad de ejemplos de código y listas detalladas de los diversos argumentos y eventos para cada componente, que lo ayudarán a implementar sus propias aplicaciones de audio personalizadas basadas en navegador.
A medida que ambas API sigan creciendo, se abrirán algunas posibilidades muy interesantes para los desarrolladores de JavaScript; permitiéndonos desarrollar DAW con todas las funciones, basado en navegador, que podrá competir con sus equivalentes Flash. Y para los desarrolladores de escritorio, también puede comenzar a crear sus propias aplicaciones multiplataforma utilizando herramientas como node-webkit. Con suerte, esto generará una nueva generación de herramientas musicales para audiófilos que empoderará a los usuarios al cerrar la brecha entre el mundo físico y la nube.