Tutorial MIDI: Criando aplicativos de áudio baseados em navegador controlados por hardware MIDI

Publicados: 2022-03-11

Enquanto a Web Audio API está crescendo em popularidade, especialmente entre os desenvolvedores de jogos HTML5, a Web MIDI API ainda é pouco conhecida entre os desenvolvedores front-end. Uma grande parte disso provavelmente tem a ver com a atual falta de suporte e documentação acessível; a API Web MIDI atualmente é suportada apenas no Google Chrome, desde que você habilite um sinalizador especial para ela. Atualmente, os fabricantes de navegadores dão pouca ênfase a essa API, pois ela está planejada para fazer parte do padrão ES7.

Projetado no início dos anos 80 por vários representantes da indústria da música, MIDI (abreviação de Musical Instrument Digital Interface), é um protocolo de comunicação padrão para dispositivos de música eletrônica. Embora outros protocolos, como o OSC, tenham sido desenvolvidos desde então; trinta anos depois, o MIDI ainda é o protocolo de comunicação de fato para os fabricantes de hardware de áudio. Você terá dificuldade em encontrar um produtor musical moderno que não possua pelo menos um dispositivo MIDI em seu estúdio.

Com o rápido desenvolvimento e adoção da API de áudio da Web, agora podemos começar a criar aplicativos baseados em navegador que preenchem a lacuna entre a nuvem e o mundo físico. A API Web MIDI não apenas nos permite construir sintetizadores e efeitos de áudio, mas podemos até mesmo começar a construir DAW (Digital Audio Workstation) baseada em navegador semelhante em recursos e desempenho às suas contrapartes atuais baseadas em flash (confira Audiotool, por exemplo ).

Neste tutorial de MIDI, vou guiá-lo pelos conceitos básicos da API Web MIDI e construiremos um monossintetizador simples que você poderá tocar com seu dispositivo MIDI favorito. O código-fonte completo está disponível aqui e você pode testar a demonstração ao vivo diretamente. Se você não possui um dispositivo MIDI, ainda pode seguir este tutorial verificando a ramificação 'teclado' do repositório GitHub, que permite suporte básico para o teclado do seu computador, para que você possa tocar notas e alterar oitavas. Esta também é a versão que está disponível como demonstração ao vivo. No entanto, devido a limitações do hardware do computador, velocidade e desafinação são desabilitadas sempre que você usa o teclado do computador para controlar o sintetizador. Consulte o arquivo leia-me no GitHub para ler sobre o mapeamento de chave/nota.

Tutorial midi da Toptal

Pré-requisitos do tutorial Midi

Você precisará do seguinte para este tutorial MIDI:

  • Google Chrome (versão 38 ou superior) com o sinalizador #enable-web-midi ativado
  • (Opcionalmente) Um dispositivo MIDI, que pode acionar notas, conectado ao seu computador

Também usaremos o Angular.js para trazer um pouco de estrutura ao nosso aplicativo; portanto, o conhecimento básico da estrutura é um pré-requisito.

Começando

Vamos modularizar nosso aplicativo MIDI desde o início, separando-o em 3 módulos:

  • WebMIDI: manipulação dos vários dispositivos MIDI conectados ao seu computador
  • WebAudio: fornecendo a fonte de áudio para nosso sintetizador
  • WebSynth: conectando a interface da web ao mecanismo de áudio

Um módulo de App lidará com a interação do usuário com a interface do usuário da web. Nossa estrutura de aplicação pode ser um pouco assim:

 |- app |-- js |--- midi.js |--- audio.js |--- synth.js |--- app.js |- index.html

Você também deve instalar as seguintes bibliotecas para ajudá-lo a construir seu aplicativo: Angular.js, Bootstrap e jQuery. Provavelmente a maneira mais fácil de instalá-los é via Bower.

O Módulo WebMIDI: Conectando-se com o mundo real

Vamos começar a descobrir como usar o MIDI conectando nossos dispositivos MIDI ao nosso aplicativo. Para isso, criaremos uma fábrica simples retornando um único método. Para conectar aos nossos dispositivos MIDI através da API Web MIDI, precisamos chamar o 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 }; }]);

E é mais ou menos isso!

O método requestMIDIAccess retorna uma promessa, então podemos apenas retorná-la diretamente e manipular o resultado da promessa no controller do nosso aplicativo:

 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 mencionado, o método requestMIDIAccess retorna uma promessa, passando um objeto para o método then , com duas propriedades: entradas e saídas.

Nas versões anteriores do Chrome, essas duas propriedades eram métodos que permitiam recuperar uma matriz de dispositivos de entrada e saída diretamente. No entanto, nas atualizações mais recentes, essas propriedades agora são objetos. Isso faz uma grande diferença, já que agora precisamos chamar o método de values nos objetos de entrada ou saída para recuperar a lista de dispositivos correspondente. Esse método atua como uma função geradora e retorna um iterador. Novamente, essa API deve fazer parte do ES7; portanto, implementar o comportamento do tipo gerador faz sentido, embora não seja tão direto quanto a implementação original.

Finalmente, podemos recuperar o número de dispositivos através da propriedade size do objeto iterador. Se houver pelo menos um dispositivo, simplesmente iteramos o resultado chamando o next método do objeto iterador e enviando cada dispositivo para um array definido no $scope. No front-end, podemos implementar uma caixa de seleção simples que listará todos os dispositivos de entrada disponíveis e nos permitirá escolher qual dispositivo queremos usar como dispositivo ativo para controlar o sintetizador da 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>

Ligamos esta caixa de seleção a uma variável $scope chamada activeDevice que usaremos mais tarde para conectar este dispositivo ativo ao sintetizador.

conecte este dispositivo ativo ao sintetizador

O Módulo WebAudio: Fazendo barulho

A API WebAudio nos permite não apenas reproduzir arquivos de som, mas também gerar sons recriando os componentes essenciais dos sintetizadores, como osciladores, filtros e nós de ganho, entre outros.

Criar um oscilador

O papel dos osciladores é produzir uma forma de onda. Existem vários tipos de formas de onda, entre as quais quatro são suportadas na API WebAudio: senoidal, quadrada, triangular e dente de serra. Diz-se que as formas de onda “oscilam” em uma determinada frequência, mas também é possível definir sua própria tabela de ondas personalizada, se necessário. Uma certa faixa de frequências é audível por seres humanos - eles são conhecidos como sons. Alternativamente, quando estão oscilando em baixas frequências, os osciladores também podem nos ajudar a construir LFOs (“oscilador de baixa frequência”) para que possamos modular nossos sons (mas isso está além do escopo deste tutorial).

A primeira coisa que precisamos fazer para criar algum som é instanciar um novo AudioContext :

 function _createContext() { self.ctx = new $window.AudioContext(); }

A partir daí, podemos instanciar qualquer um dos componentes disponibilizados pela API WebAudio. Como podemos criar várias instâncias de cada componente, faz sentido criar serviços para poder criar instâncias novas e exclusivas dos componentes de que precisamos. Vamos começar criando o serviço para gerar um novo oscilador:

 angular .module('WebAudio', []) .service('OSC', function() { var self; function Oscillator(ctx) { self = this; self.osc = ctx.createOscillator(); return self; } });

Agora podemos instanciar novos osciladores à nossa vontade, passando como argumento a instância AudioContext que criamos anteriormente. Para facilitar as coisas no futuro, adicionaremos alguns métodos de wrapper - mero açúcar sintático - e retornaremos a função 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;

Criar um filtro multipass e um controle de volume

Precisamos de mais dois componentes para completar nosso mecanismo de áudio básico: um filtro multipass, para dar um pouco de forma ao nosso som, e um nó de ganho para controlar o volume do nosso som e ligar e desligar o volume. Para isso, podemos proceder da mesma forma que fizemos para o oscilador: criar serviços retornando uma função com alguns métodos wrapper. Tudo o que precisamos fazer é fornecer a instância AudioContext e chamar o método apropriado.

Criamos um filtro chamando o método createBiquadFilter da instância AudioContext:

 ctx.createBiquadFilter();

Da mesma forma, para um nó de ganho, chamamos o método createGain :

 ctx.createGain();

O módulo WebSynth: conectando as coisas

Agora estamos quase prontos para construir nossa interface de sintetizador e conectar dispositivos MIDI à nossa fonte de áudio. Primeiro, precisamos conectar nosso mecanismo de áudio e prepará-lo para receber notas MIDI. Para conectar o mecanismo de áudio, simplesmente criamos novas instâncias dos componentes de que precisamos e, em seguida, os “conectamos” usando o método connect disponível para cada instância dos componentes. O método connect recebe um argumento, que é simplesmente o componente ao qual você deseja conectar a instância atual. É possível orquestrar uma cadeia de componentes mais elaborada, pois o método connect pode conectar um nó a vários moduladores (tornando possível implementar coisas como cross-fading e muito mais).

 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 a fiação interna do nosso mecanismo de áudio. Você pode brincar um pouco e experimentar diferentes combinações de fiação, mas lembre-se de diminuir o volume para evitar ficar surdo. Agora podemos conectar a interface MIDI ao nosso aplicativo e enviar mensagens MIDI para o mecanismo de áudio. Vamos configurar um observador na caixa de seleção do dispositivo para virtualmente “conectá-lo” ao nosso sintetizador. Em seguida, ouviremos as mensagens MIDI vindas do dispositivo e passaremos as informações para o mecanismo de áudio:

 // 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; }

Aqui, estamos ouvindo eventos MIDI do dispositivo, analisando os dados do objeto MidiEvent e passando-os para o método apropriado; noteOn ou noteOff , com base no código do evento (144 para noteOn, 128 para noteOff). Agora podemos adicionar a lógica nos respectivos métodos no módulo de áudio para realmente gerar um som:

 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); } }

Algumas coisas estão acontecendo aqui. No método noteOn , primeiro enviamos a nota atual para uma matriz de notas. Mesmo que estejamos construindo um monossintetizador (o que significa que só podemos tocar uma nota por vez), ainda podemos ter vários dedos ao mesmo tempo no teclado. Então, precisamos enfileirar todas essas notas para que quando soltarmos uma nota, a próxima seja tocada. Em seguida, precisamos parar o oscilador para atribuir a nova frequência, que convertemos de uma nota MIDI (escala de 0 a 127) para um valor de frequência real com um pouco de matemática:

 function _mtof(note) { return 440 * Math.pow(2, (note - 69) / 12); }

No método noteOff , primeiro começamos localizando a nota no array de notas ativas e removendo-a. Então, se for a única nota no array, simplesmente desligamos o volume.

O segundo argumento do método setVolume é o tempo de transição, ou seja, quanto tempo leva para o ganho atingir o novo valor de volume. Em termos musicais, se a nota estiver ligada, seria o equivalente ao tempo de ataque, e se a nota estiver desligada, equivale ao tempo de liberação.

O Módulo WebAnalyser: Visualizando nosso som

Outro recurso interessante que podemos adicionar ao nosso sintetizador é um nó analisador, que nos permite exibir a forma de onda do nosso som usando o canvas para renderizá-lo. Criar um nó analisador é um pouco mais complicado do que outros objetos AudioContext, pois requer também criar um nó scriptProcessor para realmente realizar a análise. Começamos selecionando o elemento canvas no 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; }

Em seguida, adicionamos um método connect , no qual criaremos o analisador e o processador 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); } };

Primeiro, criamos um objeto scriptProcessor e o conectamos ao destino. Em seguida, criamos o próprio analisador, que alimentamos com a saída de áudio do oscilador ou filtro. Observe como ainda precisamos conectar a saída de áudio ao destino para que possamos ouvi-lo! Também precisamos definir as cores gradientes do nosso gráfico - isso é feito chamando o método createLinearGradient do elemento canvas.

Finalmente, o scriptProcessor irá disparar um evento 'audioprocess' em um intervalo; quando esse evento é acionado, calculamos as frequências médias capturadas pelo analisador, limpamos a tela e redesenhamos o novo gráfico de frequência chamando o 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, mas não menos importante, precisaremos modificar um pouco a fiação do nosso mecanismo de áudio para acomodar esse novo 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); }

Agora temos um bom visualizador que nos permite exibir a forma de onda do nosso sintetizador em tempo real! Isso envolve um pouco de trabalho para configurar, mas é muito interessante e perspicaz, especialmente ao usar filtros.

Construindo em nosso Synth: Adicionando Velocity & Detune

Neste ponto do nosso tutorial MIDI temos um sintetizador bem legal - mas ele toca cada nota no mesmo volume. Isso ocorre porque, em vez de manipular os dados de velocidade corretamente, simplesmente configuramos o volume para um valor fixo de 1,0. Vamos começar corrigindo isso, e então veremos como podemos habilitar a roda de desafinação que você encontra nos teclados MIDI mais comuns.

Ativando a velocidade

Se você não estiver familiarizado com isso, a 'velocidade' se refere à força com que você pressiona a tecla do teclado. Com base nesse valor, o som criado parece mais suave ou mais alto.

Em nosso sintetizador de tutorial MIDI, podemos emular esse comportamento simplesmente jogando com o volume do nó de ganho. Para fazer isso, primeiro precisamos fazer um pouco de matemática para converter os dados MIDI em um valor flutuante entre 0,0 e 1,0 para passar para o nó de ganho:

 function _vtov (velocity) { return (velocity / 127).toFixed(2); }

A faixa de velocidade de um dispositivo MIDI é de 0 a 127, então simplesmente dividimos esse valor por 127 e retornamos um valor flutuante com duas casas decimais. Então, podemos atualizar o método _noteOn para passar o valor calculado para o nó de ganho:

 self.amp.setVolume(_vtov(velocity), self.settings.attack);

E é isso! Agora, quando tocamos nosso sintetizador, notamos que os volumes variam de acordo com a força com que batemos nas teclas do teclado.

Ativando a roda Detune em seu teclado MIDI

A maioria dos teclados MIDI possui uma roda de desafinação; a roda permite que você altere ligeiramente a frequência da nota que está sendo tocada, criando um efeito interessante conhecido como 'desafinação'. Isso é bastante fácil de implementar à medida que você aprende a usar o MIDI, já que a roda detune também dispara um evento MidiMessage com seu próprio código de evento (224), que podemos ouvir e agir recalculando o valor da frequência e atualizando o oscilador.

Primeiro, precisamos capturar o evento em nosso sintetizador. Para fazer isso, adicionamos um caso extra à instrução switch que criamos no retorno de chamada _onmidimessage :

 case 224: // the detune value is the third argument of the MidiEvent.data array Engine.detune(e.data[2]); break;

Em seguida, definimos o método detune no mecanismo de áudio:

 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; } } }

O valor de desafinação padrão é 64, o que significa que não há desafinação aplicada, portanto, neste caso, simplesmente passamos a frequência atual para o oscilador.

Por fim, também precisamos atualizar o método _noteOff , para levar em consideração a detune caso outra nota seja enfileirada:

 self.osc1.setFrequency(self.currentFreq + self.detuneAmount, self.settings.portamento);

Criando a Interface

Até agora, criamos apenas uma caixa de seleção para poder selecionar nosso dispositivo MIDI e um visualizador de forma de onda, mas não temos a possibilidade de modificar o som diretamente interagindo com a página da web. Vamos criar uma interface muito simples usando elementos de formulário comuns e vinculá-los ao nosso mecanismo de áudio.

Criando um layout para a interface

Vamos criar vários elementos de formulário para controlar o som do nosso sintetizador:

  • Um grupo de rádio para selecionar o tipo de oscilador
  • Uma caixa de seleção para ativar/desativar o filtro
  • Um grupo de rádio para selecionar o tipo de filtro
  • Duas faixas para controlar a frequência e ressonância do filtro
  • Duas faixas para controlar o ataque e liberação do nó de ganho

Criando um documento HTML para nossa interface, devemos terminar com algo assim:

 <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 a interface do usuário para parecer chique não é algo que abordarei neste tutorial básico de MIDI; em vez disso, podemos salvá-lo como um exercício para depois polir a interface do usuário, talvez algo assim:

interface de usuário midi polida

Vinculando a interface ao mecanismo de áudio

Devemos definir alguns métodos para vincular esses controles ao nosso mecanismo de áudio.

Controlando o oscilador

Para o oscilador, precisamos apenas de um método que nos permita definir o tipo de oscilador:

 Oscillator.prototype.setOscType = function(type) { if(type) { self.osc.type = type; } }

Controlando o filtro

Para o filtro, precisamos de três controles: um para o tipo de filtro, um para a frequência e outro para a ressonância. Também podemos conectar os métodos _connectFilter e _disconnectFilter ao valor da caixa de seleção.

 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; } }

Controlando o Ataque e Ressonância

Para moldar um pouco nosso som, podemos alterar os parâmetros de ataque e liberação do nó de ganho. Precisamos de dois métodos para isso:

 function _setAttack(a) { if(a) { self.settings.attack = a / 1000; } } function _setRelease(r) { if(r) { self.settings.release = r / 1000; } }

Configurando observadores

Por fim, no controlador do nosso aplicativo, precisamos apenas configurar alguns observadores e vinculá-los aos vários métodos que acabamos de criar:

 $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);

Conclusão

Muitos conceitos foram abordados neste tutorial de MIDI; principalmente, descobrimos como usar a API WebMIDI, que é bastante não documentada, além da especificação oficial do W3C. A implementação do Google Chrome é bastante direta, embora a mudança para um objeto iterador para os dispositivos de entrada e saída exija um pouco de refatoração para código legado usando a implementação antiga.

Quanto à API WebAudio, esta é uma API muito rica e abordamos apenas alguns de seus recursos neste tutorial. Ao contrário da API WebMIDI, a API WebAudio está muito bem documentada, em particular na Mozilla Developer Network. A Mozilla Developer Network contém uma infinidade de exemplos de código e listas detalhadas dos vários argumentos e eventos para cada componente, que o ajudarão a implementar seus próprios aplicativos de áudio personalizados baseados em navegador.

À medida que ambas as APIs continuam a crescer, abrirão algumas possibilidades muito interessantes para desenvolvedores de JavaScript; permitindo-nos desenvolver um DAW baseado em navegador completo que será capaz de competir com seus equivalentes em Flash. E para desenvolvedores de desktop, você também pode começar a criar seus próprios aplicativos multiplataforma usando ferramentas como node-webkit. Felizmente, isso gerará uma nova geração de ferramentas de música para audiófilos que capacitarão os usuários, preenchendo a lacuna entre o mundo físico e a nuvem.