Tutorial MIDI: creazione di applicazioni audio basate su browser controllate dall'hardware MIDI
Pubblicato: 2022-03-11Sebbene l'API Web Audio stia diventando sempre più popolare, soprattutto tra gli sviluppatori di giochi HTML5, l'API Web MIDI è ancora poco conosciuta tra gli sviluppatori frontend. Gran parte di questo ha probabilmente a che fare con la sua attuale mancanza di supporto e documentazione accessibile; l'API Web MIDI è attualmente supportata solo in Google Chrome, a condizione che tu abiliti un flag speciale per essa. I produttori di browser attualmente pongono poca enfasi su questa API, poiché è previsto che faccia parte dello standard ES7.
Progettato nei primi anni '80 da diversi rappresentanti dell'industria musicale, MIDI (abbreviazione di Musical Instrument Digital Interface), è un protocollo di comunicazione standard per dispositivi di musica elettronica. Anche se da allora sono stati sviluppati altri protocolli, come OSC; trent'anni dopo, il MIDI è ancora il protocollo di comunicazione de facto per i produttori di hardware audio. Ti sarà difficile trovare un produttore musicale moderno che non possieda almeno un dispositivo MIDI nel suo studio.
Con il rapido sviluppo e adozione dell'API Web Audio, ora possiamo iniziare a creare applicazioni basate su browser che colmano il divario tra il cloud e il mondo fisico. Non solo l'API Web MIDI ci consente di creare sintetizzatori ed effetti audio, ma possiamo anche iniziare a creare DAW (Digital Audio Workstation) basata su browser simili per funzionalità e prestazioni alle loro attuali controparti basate su flash (controlla Audiotool, ad esempio ).
In questo tutorial MIDI, ti guiderò attraverso le basi dell'API MIDI Web e costruiremo un semplice sintetizzatore mono che potrai suonare con il tuo dispositivo MIDI preferito. Il codice sorgente completo è disponibile qui e puoi testare direttamente la demo dal vivo. Se non possiedi un dispositivo MIDI, puoi comunque seguire questo tutorial controllando il ramo "tastiera" del repository GitHub, che abilita il supporto di base per la tastiera del tuo computer, così puoi suonare note e cambiare ottave. Questa è anche la versione disponibile come demo dal vivo. Tuttavia, a causa delle limitazioni dell'hardware del computer, velocity e detune sono entrambe disabilitate ogni volta che si utilizza la tastiera del computer per controllare il sintetizzatore. Fare riferimento al file readme su GitHub per leggere la mappatura chiave/nota.
Midi Tutorial Prerequisiti
Avrai bisogno di quanto segue per questo tutorial MIDI:
- Google Chrome (versione 38 o successive) con il flag
#enable-web-midi
abilitato - (Facoltativo) Un dispositivo MIDI, in grado di attivare note, collegato al computer
Useremo anche Angular.js per dare un po' di struttura alla nostra applicazione; pertanto, la conoscenza di base del framework è un prerequisito.
Iniziare
Modularizzeremo la nostra applicazione MIDI da zero separandola in 3 moduli:
- WebMIDI: gestione dei vari dispositivi MIDI collegati al tuo computer
- WebAudio: fornisce la sorgente audio per il nostro sintetizzatore
- WebSynth: collegamento dell'interfaccia web al motore audio
Un modulo App
gestirà l'interazione dell'utente con l'interfaccia utente web. La nostra struttura dell'applicazione potrebbe assomigliare un po' a questa:
|- app |-- js |--- midi.js |--- audio.js |--- synth.js |--- app.js |- index.html
Dovresti anche installare le seguenti librerie per aiutarti a costruire la tua applicazione: Angular.js, Bootstrap e jQuery. Probabilmente il modo più semplice per installarli è tramite Bower.
Il modulo WebMIDI: connessione con il mondo reale
Iniziamo a capire come utilizzare il MIDI collegando i nostri dispositivi MIDI alla nostra applicazione. Per fare ciò, creeremo una semplice fabbrica restituendo un unico metodo. Per connetterci ai nostri dispositivi MIDI tramite la Web MIDI API, dobbiamo chiamare il metodo 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 questo è praticamente tutto!
Il metodo requestMIDIAccess
restituisce una promessa, quindi possiamo semplicemente restituirla direttamente e gestire il risultato della promessa nel controller della nostra app:
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); }); }]);
Come accennato, il metodo requestMIDIAccess
restituisce una promessa, passando un oggetto al metodo then
, con due proprietà: input e output.
Nelle versioni precedenti di Chrome, queste due proprietà erano metodi che ti permettevano di recuperare direttamente una serie di dispositivi di input e output. Tuttavia, negli ultimi aggiornamenti, queste proprietà sono ora oggetti. Questo fa una bella differenza, dal momento che ora dobbiamo chiamare il metodo dei values
sull'oggetto input o output per recuperare l'elenco corrispondente di dispositivi. Questo metodo funge da funzione generatore e restituisce un iteratore. Ancora una volta, questa API dovrebbe far parte di ES7; pertanto, l'implementazione di un comportamento simile a un generatore ha senso, anche se non è così semplice come l'implementazione originale.
Infine, possiamo recuperare il numero di dispositivi tramite la proprietà size
dell'oggetto iteratore. Se è presente almeno un dispositivo, ripetiamo semplicemente il risultato chiamando il metodo next
dell'oggetto iteratore e spingendo ciascun dispositivo su un array definito nell'ambito di $. Sul front-end, possiamo implementare una semplice casella di selezione che elencherà tutti i dispositivi di input disponibili e ci permetterà di scegliere quale dispositivo vogliamo utilizzare come dispositivo attivo per controllare il web synth:
<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>
Abbiamo associato questa casella di selezione a una variabile $ scope chiamata activeDevice
che useremo in seguito per connettere questo dispositivo attivo al sintetizzatore.
Il modulo WebAudio: fare rumore
L'API WebAudio ci consente non solo di riprodurre file audio, ma anche di generare suoni ricreando i componenti essenziali di sintetizzatori come oscillatori, filtri e nodi di guadagno, tra gli altri.
Crea un oscillatore
Il ruolo degli oscillatori è quello di produrre una forma d'onda. Esistono vari tipi di forme d'onda, di cui quattro sono supportate nell'API WebAudio: sinusoidale, quadrata, triangolare e a dente di sega. Si dice che le forme d'onda "oscillino" a una certa frequenza, ma è anche possibile definire la propria tavola d'onda personalizzata, se necessario. Una certa gamma di frequenze sono udibili dagli esseri umani: sono conosciute come suoni. In alternativa, quando oscillano a basse frequenze, gli oscillatori possono anche aiutarci a costruire LFO ("oscillatore a bassa frequenza") in modo da poter modulare i nostri suoni (ma questo va oltre lo scopo di questo tutorial).
La prima cosa che dobbiamo fare per creare un suono è creare un'istanza di un nuovo AudioContext
:
function _createContext() { self.ctx = new $window.AudioContext(); }
Da lì, possiamo creare un'istanza di qualsiasi componente reso disponibile dall'API WebAudio. Poiché potremmo creare più istanze di ciascun componente, ha senso creare servizi per poter creare nuove istanze uniche dei componenti di cui abbiamo bisogno. Iniziamo creando il servizio per generare un nuovo oscillatore:
angular .module('WebAudio', []) .service('OSC', function() { var self; function Oscillator(ctx) { self = this; self.osc = ctx.createOscillator(); return self; } });
Ora possiamo istanziare nuovi oscillatori a nostro piacimento, passando come argomento l'istanza AudioContext che abbiamo creato in precedenza. Per rendere le cose più facili, aggiungeremo alcuni metodi wrapper - semplice zucchero sintattico - e restituiremo la funzione 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;
Crea un filtro multipass e un controllo del volume
Abbiamo bisogno di altri due componenti per completare il nostro motore audio di base: un filtro multipass, per dare un po' di forma al nostro suono, e un nodo di guadagno per controllare il volume del nostro suono e attivare e disattivare il volume. Per fare ciò, possiamo procedere nello stesso modo in cui abbiamo fatto per l'oscillatore: creare servizi che restituiscono una funzione con alcuni metodi wrapper. Tutto quello che dobbiamo fare è fornire l'istanza AudioContext e chiamare il metodo appropriato.
Creiamo un filtro chiamando il metodo createBiquadFilter
dell'istanza AudioContext:
ctx.createBiquadFilter();
Allo stesso modo, per un nodo di guadagno, chiamiamo il metodo createGain
:
ctx.createGain();
Il modulo WebSynth: cablaggio
Ora siamo quasi pronti per costruire la nostra interfaccia synth e collegare dispositivi MIDI alla nostra sorgente audio. Per prima cosa, dobbiamo collegare il nostro motore audio insieme e prepararlo per ricevere le note MIDI. Per connettere il motore audio, creiamo semplicemente nuove istanze dei componenti di cui abbiamo bisogno, quindi le "connettiamo" insieme utilizzando il metodo di connect
disponibile per le istanze di ciascun componente. Il metodo connect
accetta un argomento, che è semplicemente il componente a cui vuoi connettere l'istanza corrente. È possibile orchestrare una catena di componenti più elaborata poiché il metodo di connect
può connettere un nodo a più modulatori (rendendo possibile implementare cose come dissolvenza incrociata e altro).
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); }
Abbiamo appena costruito il cablaggio interno del nostro motore audio. Puoi giocare un po' e provare diverse combinazioni di cablaggio, ma ricorda di abbassare il volume per evitare di diventare sordo. Ora possiamo collegare l'interfaccia MIDI alla nostra applicazione e inviare messaggi MIDI al motore audio. Imposteremo un watcher sulla casella di selezione del dispositivo per "collegarlo" virtualmente al nostro sintetizzatore. Ascolteremo quindi i messaggi MIDI provenienti dal dispositivo e passeremo le informazioni al motore 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; }
Qui ascoltiamo gli eventi MIDI dal dispositivo, analizziamo i dati dall'oggetto MidiEvent e li trasmettiamo al metodo appropriato; noteOn
o noteOff
, in base al codice evento (144 per noteOn, 128 per noteOff). Ora possiamo aggiungere la logica nei rispettivi metodi nel modulo audio per generare effettivamente un suono:
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); } }
Qui stanno accadendo alcune cose. Nel metodo noteOn
, inseriamo prima la nota corrente in un array di note. Anche se stiamo costruendo un monosynth (il che significa che possiamo suonare solo una nota alla volta), possiamo comunque avere più dita contemporaneamente sulla tastiera. Quindi, dobbiamo mettere in coda tutte queste note in modo che quando rilasciamo una nota, venga suonata quella successiva. Dobbiamo quindi fermare l'oscillatore per assegnare la nuova frequenza, che convertiamo da una nota MIDI (scala da 0 a 127) a un valore di frequenza effettivo con un po' di matematica:
function _mtof(note) { return 440 * Math.pow(2, (note - 69) / 12); }
Nel metodo noteOff
, iniziamo innanzitutto trovando la nota nell'array di note attive e rimuovendola. Quindi, se fosse l'unica nota nell'array, spegniamo semplicemente il volume.
Il secondo argomento del metodo setVolume
è il tempo di transizione, ovvero quanto tempo impiega il guadagno per raggiungere il nuovo valore di volume. In termini musicali, se la nota è attiva, sarebbe l'equivalente del tempo di attacco, e se la nota è spenta, è l'equivalente del tempo di rilascio.

Il modulo WebAnalyser: visualizzare il nostro suono
Un'altra caratteristica interessante che possiamo aggiungere al nostro sintetizzatore è un nodo analizzatore, che ci permette di visualizzare la forma d'onda del nostro suono usando la tela per renderla. La creazione di un nodo analizzatore è un po' più complicata rispetto ad altri oggetti AudioContext, poiché richiede anche la creazione di un nodo scriptProcessor per eseguire effettivamente l'analisi. Iniziamo selezionando l'elemento canvas sul 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; }
Quindi, aggiungiamo un metodo di connect
, in cui creeremo sia l'analizzatore che il processore di script:
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); } };
Innanzitutto, creiamo un oggetto scriptProcessor e lo colleghiamo alla destinazione. Quindi, creiamo l'analizzatore stesso, che alimentiamo con l'uscita audio dall'oscillatore o dal filtro. Nota come dobbiamo ancora collegare l'uscita audio alla destinazione in modo che possiamo ascoltarla! Abbiamo anche bisogno di definire i colori del gradiente del nostro grafico: questo viene fatto chiamando il metodo createLinearGradient
dell'elemento canvas.
Infine, lo scriptProcessor attiverà un evento 'audioprocess' su un intervallo; quando questo evento viene attivato, calcoliamo le frequenze medie catturate dall'analizzatore, svuotiamo la tela e ridisegniamo il nuovo grafico delle frequenze chiamando il metodo 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)); } }
Ultimo ma non meno importante, dovremo modificare un po' il cablaggio del nostro motore audio per accogliere questo nuovo 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); }
Ora abbiamo un bel visualizzatore che ci permette di visualizzare la forma d'onda del nostro sintetizzatore in tempo reale! Ciò comporta un po' di lavoro da configurare, ma è molto interessante e perspicace, specialmente quando si utilizzano i filtri.
Costruire sul nostro sintetizzatore: aggiungere velocità e detune
A questo punto nel nostro tutorial MIDI abbiamo un sintetizzatore piuttosto interessante, ma suona tutte le note allo stesso volume. Questo perché invece di gestire correttamente i dati di velocità, impostiamo semplicemente il volume su un valore fisso di 1,0. Iniziamo col sistemarlo e poi vedremo come abilitare la ruota di detune che trovi sulle tastiere MIDI più comuni.
Abilitazione della velocità
Se non lo conosci, la "velocità" si riferisce alla forza con cui hai premuto il tasto sulla tastiera. Sulla base di questo valore, il suono creato sembra più debole o più forte.
Nel nostro sintetizzatore MIDI tutorial, possiamo emulare questo comportamento semplicemente giocando con il volume del nodo di guadagno. Per fare ciò, dobbiamo prima fare un po' di matematica per convertire i dati MIDI in un valore float compreso tra 0.0 e 1.0 da passare al nodo di guadagno:
function _vtov (velocity) { return (velocity / 127).toFixed(2); }
L'intervallo di velocità di un dispositivo MIDI è compreso tra 0 e 127, quindi dividiamo semplicemente quel valore per 127 e restituiamo un valore float con due decimali. Quindi, possiamo aggiornare il metodo _noteOn
per passare il valore calcolato al nodo di guadagno:
self.amp.setVolume(_vtov(velocity), self.settings.attack);
E questo è tutto! Ora, quando suoniamo il nostro sintetizzatore, noteremo che i volumi variano in base alla forza con cui premiamo i tasti sulla nostra tastiera.
Abilitazione della Detune Wheel sulla tua tastiera MIDI
La maggior parte delle tastiere MIDI è dotata di una rotella di detune; la rotellina permette di modificare leggermente la frequenza della nota attualmente suonata, creando un interessante effetto noto come 'detune'. Questo è abbastanza facile da implementare mentre impari a usare il MIDI, poiché la ruota di detune attiva anche un evento MidiMessage con il proprio codice evento (224), che possiamo ascoltare e agire ricalcolando il valore della frequenza e aggiornando l'oscillatore.
Per prima cosa, dobbiamo catturare l'evento nel nostro sintetizzatore. Per fare ciò, aggiungiamo un caso in più all'istruzione switch che abbiamo creato nel callback _onmidimessage
:
case 224: // the detune value is the third argument of the MidiEvent.data array Engine.detune(e.data[2]); break;
Quindi, definiamo il metodo di detune
sul motore 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; } } }
Il valore di detune predefinito è 64, il che significa che non viene applicata alcuna detune, quindi in questo caso passiamo semplicemente la frequenza corrente all'oscillatore.
Infine, dobbiamo anche aggiornare il metodo _noteOff
, per prendere in considerazione la detune nel caso in cui un'altra nota sia in coda:
self.osc1.setFrequency(self.currentFreq + self.detuneAmount, self.settings.portamento);
Creazione dell'interfaccia
Finora abbiamo creato solo una casella di selezione per poter selezionare il nostro dispositivo MIDI e un visualizzatore di forme d'onda, ma non abbiamo la possibilità di modificare il suono direttamente interagendo con la pagina web. Creiamo un'interfaccia molto semplice usando elementi di modulo comuni e le colleghiamo al nostro motore audio.
Creazione di un layout per l'interfaccia
Creeremo vari elementi di forma per controllare il suono del nostro sintetizzatore:
- Un gruppo radio per selezionare il tipo di oscillatore
- Una casella di controllo per abilitare/disabilitare il filtro
- Un gruppo radio per selezionare il tipo di filtro
- Due gamme per controllare la frequenza e la risonanza del filtro
- Due range per controllare l'attacco e il rilascio del nodo di guadagno
Creando un documento HTML per la nostra interfaccia, dovremmo finire con qualcosa del genere:
<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>
Decorare l'interfaccia utente in modo che appaia elegante non è qualcosa che tratterò in questo tutorial MIDI di base; invece possiamo salvarlo come esercizio per in seguito rifinire l'interfaccia utente, magari per assomigliare a questo:
Associazione dell'interfaccia al motore audio
Dovremmo definire alcuni metodi per associare questi controlli al nostro motore audio.
Controllo dell'oscillatore
Per l'oscillatore, abbiamo solo bisogno di un metodo che ci permetta di impostare il tipo di oscillatore:
Oscillator.prototype.setOscType = function(type) { if(type) { self.osc.type = type; } }
Controllo del filtro
Per il filtro, abbiamo bisogno di tre controlli: uno per il tipo di filtro, uno per la frequenza e uno per la risonanza. Possiamo anche collegare i metodi _connectFilter
e _disconnectFilter
al valore della casella di controllo.
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; } }
Controllo dell'attacco e della risonanza
Per modellare un po' il nostro suono, possiamo cambiare i parametri di attacco e rilascio del nodo di guadagno. Abbiamo bisogno di due metodi per questo:
function _setAttack(a) { if(a) { self.settings.attack = a / 1000; } } function _setRelease(r) { if(r) { self.settings.release = r / 1000; } }
Impostazione degli osservatori
Infine, nel controller della nostra app, dobbiamo solo configurare alcuni watcher e associarli ai vari metodi che abbiamo appena creato:
$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);
Conclusione
Molti concetti sono stati trattati in questo tutorial MIDI; per lo più, abbiamo scoperto come utilizzare l'API WebMIDI, che è abbastanza non documentata a parte le specifiche ufficiali del W3C. L'implementazione di Google Chrome è piuttosto semplice, sebbene il passaggio a un oggetto iteratore per i dispositivi di input e output richieda un po' di refactoring per il codice legacy utilizzando la vecchia implementazione.
Per quanto riguarda l'API WebAudio, questa è un'API molto ricca e in questo tutorial abbiamo trattato solo alcune delle sue capacità. A differenza dell'API WebMIDI, l'API WebAudio è molto ben documentata, in particolare su Mozilla Developer Network. Il Mozilla Developer Network contiene una pletora di esempi di codice ed elenchi dettagliati dei vari argomenti ed eventi per ciascun componente, che ti aiuteranno a implementare le tue applicazioni audio personalizzate basate su browser.
Poiché entrambe le API continuano a crescere, si apriranno alcune possibilità molto interessanti per gli sviluppatori JavaScript; permettendoci di sviluppare DAW complete, basate su browser, in grado di competere con i loro equivalenti Flash. E per gli sviluppatori desktop, puoi anche iniziare a creare le tue applicazioni multipiattaforma utilizzando strumenti come node-webkit. Si spera che questo genererà una nuova generazione di strumenti musicali per audiofili che daranno potere agli utenti colmando il divario tra il mondo fisico e il cloud.