Tutorial MIDI: Crearea aplicațiilor audio bazate pe browser controlate de hardware MIDI
Publicat: 2022-03-11În timp ce API-ul Web Audio crește în popularitate, în special în rândul dezvoltatorilor de jocuri HTML5, API-ul Web MIDI este încă puțin cunoscut printre dezvoltatorii de front-end. O mare parte din acest lucru probabil are de-a face cu lipsa actuală de suport și documentație accesibilă; API-ul Web MIDI este în prezent acceptat numai în Google Chrome, cu condiția să activați un semnal special pentru acesta. Producătorii de browsere pun în prezent puțin accent pe acest API, deoarece este planificat să facă parte din standardul ES7.
Proiectat la începutul anilor 80 de câțiva reprezentanți ai industriei muzicale, MIDI (prescurtarea de la Musical Instrument Digital Interface), este un protocol de comunicație standard pentru dispozitivele de muzică electronică. Chiar dacă alte protocoale, cum ar fi OSC, au fost dezvoltate de atunci; treizeci de ani mai târziu, MIDI este încă protocolul de comunicare de facto pentru producătorii de hardware audio. Veți fi greu să găsiți un producător de muzică modern care să nu dețină cel puțin un dispozitiv MIDI în studioul său.
Odată cu dezvoltarea și adoptarea rapidă a API-ului Web Audio, acum putem începe să construim aplicații bazate pe browser care să creeze o punte între cloud și lumea fizică. Nu numai că API-ul Web MIDI ne permite să construim sintetizatoare și efecte audio, dar putem chiar să începem să construim DAW (Digital Audio Workstation) bazat pe browser, similare ca caracteristici și performanțe cu omologii lor actuali bazați pe flash (consultați Audiotool, de exemplu ).
În acest tutorial MIDI, vă voi ghida prin elementele de bază ale API-ului Web MIDI și vom construi un monosynth simplu pe care îl veți putea juca cu dispozitivul MIDI preferat. Codul sursă complet este disponibil aici și puteți testa direct demonstrația live. Dacă nu dețineți un dispozitiv MIDI, puteți în continuare să urmați acest tutorial verificând ramura „tastatură” a depozitului GitHub, care permite suportul de bază pentru tastatura computerului, astfel încât să puteți reda note și să schimbați octave. Aceasta este și versiunea disponibilă ca demo live. Cu toate acestea, din cauza limitărilor hardware-ului computerului, viteza și detonarea sunt ambele dezactivate ori de câte ori utilizați tastatura computerului pentru a controla sintetizatorul. Vă rugăm să consultați fișierul readme de pe GitHub pentru a citi despre maparea cheii/notelor.
Midi Tutorial Condiții preliminare
Veți avea nevoie de următoarele pentru acest tutorial MIDI:
- Google Chrome (versiunea 38 sau mai sus) cu
#enable-web-midi
activat - (Opțional) Un dispozitiv MIDI, care poate declanșa note, conectat la computer
De asemenea, vom folosi Angular.js pentru a aduce un pic de structură aplicației noastre; prin urmare, cunoașterea de bază a cadrului este o condiție prealabilă.
Noțiuni de bază
Vom modulariza aplicația noastră MIDI de la zero, separând-o în 3 module:
- WebMIDI: gestionarea diferitelor dispozitive MIDI conectate la computer
- WebAudio: furnizarea sursei audio pentru sintezatorul nostru
- WebSynth: conectarea interfeței web la motorul audio
Un modul de App
se va ocupa de interacțiunea utilizatorului cu interfața cu utilizatorul web. Structura aplicației noastre ar putea arăta cam așa:
|- app |-- js |--- midi.js |--- audio.js |--- synth.js |--- app.js |- index.html
De asemenea, ar trebui să instalați următoarele biblioteci pentru a vă ajuta să vă construiți aplicația: Angular.js, Bootstrap și jQuery. Probabil cel mai simplu mod de a le instala este prin Bower.
Modulul WebMIDI: Conectarea cu lumea reală
Să începem să ne dăm seama cum să folosim MIDI conectând dispozitivele noastre MIDI la aplicația noastră. Pentru a face acest lucru, vom crea o fabrică simplă care returnează o singură metodă. Pentru a vă conecta la dispozitivele noastre MIDI prin API-ul Web MIDI, trebuie să apelăm metoda 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 }; }]);
Și cam asta este!
Metoda requestMIDIAccess
returnează o promisiune, așa că o putem returna direct și gestiona rezultatul promisiunii în controlerul aplicației noastre:
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); }); }]);
După cum sa menționat, metoda requestMIDIAccess
returnează o promisiune, pasând un obiect metodei then
, cu două proprietăți: intrări și ieșiri.
În versiunile anterioare ale Chrome, aceste două proprietăți erau metode care vă permiteau să preluați direct o serie de dispozitive de intrare și ieșire. Cu toate acestea, în cele mai recente actualizări, aceste proprietăți sunt acum obiecte. Acest lucru face o diferență destul de mare, deoarece acum trebuie să apelăm metoda values
fie pe obiectul de intrare, fie pe obiectul de ieșire pentru a prelua lista corespunzătoare de dispozitive. Această metodă acționează ca o funcție generatoare și returnează un iterator. Din nou, acest API este menit să facă parte din ES7; prin urmare, implementarea unui comportament asemănător generatorului are sens, chiar dacă nu este la fel de simplă ca implementarea originală.
În cele din urmă, putem prelua numărul de dispozitive prin proprietatea size
a obiectului iterator. Dacă există cel puțin un dispozitiv, pur și simplu iterăm peste rezultat apelând next
metodă a obiectului iterator și împingând fiecare dispozitiv într-o matrice definită în $scope. Pe front-end, putem implementa o casetă de selectare simplă care va lista toate dispozitivele de intrare disponibile și ne va permite să alegem ce dispozitiv dorim să folosim ca dispozitiv activ pentru a controla sintetizatorul 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>
Am legat această casetă de selectare la o variabilă $scope numită activeDevice
pe care o vom folosi ulterior pentru a conecta acest dispozitiv activ la sintetizator.
Modulul WebAudio: A face zgomot
API-ul WebAudio ne permite nu numai să redăm fișiere de sunet, ci și să generăm sunete prin recrearea componentelor esențiale ale sintetizatoarelor, cum ar fi oscilatoarele, filtrele și nodurile de câștig, printre altele.
Creați un oscilator
Rolul oscilatorilor este de a scoate o formă de undă. Există diferite tipuri de forme de undă, dintre care patru sunt acceptate în API-ul WebAudio: sinusoidală, pătrată, triunghiulară și dinte de ferăstrău. Se spune că formele de undă „oscilează” la o anumită frecvență, dar este, de asemenea, posibil ca cineva să-și definească propria tabelă de undă, dacă este necesar. O anumită gamă de frecvențe sunt audibile de ființele umane - sunt cunoscute ca sunete. Alternativ, atunci când oscilează la frecvențe joase, oscilatorii ne pot ajuta, de asemenea, să construim LFO-uri („oscilator de joasă frecvență”), astfel încât să ne putem modula sunetele (dar asta depășește scopul acestui tutorial).
Primul lucru pe care trebuie să-l facem pentru a crea un sunet este să instanțiem un nou AudioContext
:
function _createContext() { self.ctx = new $window.AudioContext(); }
De acolo, putem instanția oricare dintre componentele puse la dispoziție de API-ul WebAudio. Deoarece am putea crea mai multe instanțe ale fiecărei componente, este logic să creăm servicii pentru a putea crea instanțe noi, unice ale componentelor de care avem nevoie. Să începem prin a crea serviciul pentru a genera un nou oscilator:
angular .module('WebAudio', []) .service('OSC', function() { var self; function Oscillator(ctx) { self = this; self.osc = ctx.createOscillator(); return self; } });
Acum putem instanția noi oscilatoare după dorința noastră, trecând ca argument instanța AudioContext pe care am creat-o mai devreme. Pentru a ușura lucrurile pe drum, vom adăuga câteva metode de înveliș - simplu zahăr sintactic - și vom returna funcția 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ți un filtru Multipass și un control al volumului
Avem nevoie de încă două componente pentru a completa motorul nostru audio de bază: un filtru multipass, pentru a da un pic de formă sunetului nostru și un nod de câștig pentru a controla volumul sunetului nostru și a porni și opri volumul. Pentru a face acest lucru, putem proceda în același mod în care am procedat pentru oscilator: creați servicii care returnează o funcție cu unele metode wrapper. Tot ce trebuie să facem este să furnizăm instanța AudioContext și să apelăm metoda corespunzătoare.
Creăm un filtru apelând metoda createBiquadFilter
a instanței AudioContext:
ctx.createBiquadFilter();
În mod similar, pentru un nod de câștig, numim metoda createGain
:
ctx.createGain();
Modulul WebSynth: Conectarea lucrurilor
Acum suntem aproape gata să ne construim interfața de sinteză și să conectăm dispozitivele MIDI la sursa noastră audio. Mai întâi, trebuie să conectăm motorul nostru audio și să-l pregătim pentru a primi note MIDI. Pentru a conecta motorul audio, pur și simplu creăm noi instanțe ale componentelor de care avem nevoie și apoi le „conectăm” împreună folosind metoda de connect
disponibilă pentru instanțele fiecărei componente. Metoda de connect
ia un singur argument, care este pur și simplu componenta la care doriți să conectați instanța curentă. Este posibil să se orchestreze un lanț mai elaborat de componente, deoarece metoda de connect
poate conecta un nod la mai mulți modulatori (făcând posibilă implementarea unor lucruri precum cross-fading și altele).
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); }
Tocmai am construit cablajul intern al motorului nostru audio. Puteți să vă jucați puțin și să încercați diferite combinații de cablare, dar nu uitați să reduceți volumul pentru a nu deveni surd. Acum putem conecta interfața MIDI la aplicația noastră și putem trimite mesaje MIDI motorului audio. Vom configura un observator pe caseta de selectare a dispozitivului pentru a-l „conecta” practic la sintetizatorul nostru. Apoi vom asculta mesajele MIDI care vin de pe dispozitiv și vom transmite informațiile motorului 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; }
Aici, ascultăm evenimente MIDI de pe dispozitiv, analizăm datele din obiectul MidiEvent și le transmitem la metoda corespunzătoare; fie noteOn
, fie noteOff
, pe baza codului evenimentului (144 pentru noteOn, 128 pentru noteOff). Acum putem adăuga logica în metodele respective în modulul audio pentru a genera efectiv un sunet:
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); } }
Aici se întâmplă câteva lucruri. În metoda noteOn
, mai întâi împingem nota curentă într-o serie de note. Chiar dacă construim un monosynth (adică putem cânta doar o notă la un moment dat), putem avea totuși mai multe degete simultan pe tastatură. Deci, trebuie să punem în coadă toate notele de teză, astfel încât atunci când eliberăm o notă, următoarea să fie redată. Apoi trebuie să oprim oscilatorul pentru a aloca noua frecvență, pe care o convertim dintr-o notă MIDI (scală de la 0 la 127) la o valoare reală a frecvenței cu un pic de matematică:
function _mtof(note) { return 440 * Math.pow(2, (note - 69) / 12); }
În metoda noteOff
, începem mai întâi prin a găsi nota în matricea de note active și a o elimina. Apoi, dacă era singura notă din matrice, pur și simplu oprim volumul.
Al doilea argument al metodei setVolume
este timpul de tranziție, adică cât timp durează câștigul pentru a atinge noua valoare a volumului. În termeni muzicali, dacă nota este activată, ar fi echivalentul timpului de atac, iar dacă nota este oprită, este echivalentul timpului de eliberare.

Modulul WebAnalyser: Vizualizarea sunetului nostru
O altă caracteristică interesantă pe care o putem adăuga sintetizatorului nostru este un nod de analizor, care ne permite să afișăm forma de undă a sunetului nostru folosind pânza pentru a-l reda. Crearea unui nod analizor este puțin mai complicată decât alte obiecte AudioContext, deoarece necesită și crearea unui nod scriptProcessor pentru a efectua efectiv analiza. Începem prin a selecta elementul canvas pe 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; }
Apoi, adăugăm o metodă de connect
, în care vom crea atât analizorul, cât și procesorul de 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); } };
Mai întâi, creăm un obiect scriptProcessor și îl conectăm la destinație. Apoi, creăm analizorul în sine, pe care îl alimentam cu ieșirea audio de la oscilator sau filtru. Observați cum mai trebuie să conectăm ieșirea audio la destinație, astfel încât să o putem auzi! De asemenea, trebuie să definim culorile gradient ale graficului nostru - acest lucru se face apelând metoda createLinearGradient
a elementului canvas.
În cele din urmă, scriptProcessor va declanșa un eveniment „audioprocess” la un interval; când acest eveniment este declanșat, calculăm frecvențele medii capturate de analizor, ștergem pânza și redesenăm noul grafic de frecvență apelând metoda 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)); } }
Nu în ultimul rând, va trebui să modificăm puțin cablajul motorului nostru audio pentru a găzdui această nouă componentă:
// 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); }
Acum avem un vizualizator frumos care ne permite să afișăm forma de undă a sintetizatorului nostru în timp real! Acest lucru implică puțină muncă de configurare, dar este foarte interesant și perspicace, mai ales când folosiți filtre.
Construind pe sintezatorul nostru: Adăugarea de viteză și detonare
În acest moment al tutorialului nostru MIDI avem un sintetizator destul de grozav - dar redă fiecare notă la același volum. Acest lucru se datorează faptului că, în loc să gestionăm corect datele de viteză, pur și simplu setăm volumul la o valoare fixă de 1,0. Să începem prin a remedia asta și apoi vom vedea cum putem activa roata de acordare pe care o găsiți pe cele mai comune tastaturi MIDI.
Activarea vitezei
Dacă nu sunteți familiarizat cu acesta, „viteza” se referă la cât de puternic apăsați tasta de pe tastatură. Pe baza acestei valori, sunetul creat pare fie mai blând, fie mai puternic.
În sintetizatorul nostru tutorial MIDI, putem emula acest comportament pur și simplu jucându-ne cu volumul nodului de câștig. Pentru a face acest lucru, trebuie mai întâi să facem puțină matematică pentru a converti datele MIDI într-o valoare float între 0,0 și 1,0 pentru a le trece la nodul de câștig:
function _vtov (velocity) { return (velocity / 127).toFixed(2); }
Intervalul de viteză al unui dispozitiv MIDI este de la 0 la 127, așa că pur și simplu împărțim acea valoare la 127 și returnăm o valoare float cu două zecimale. Apoi, putem actualiza metoda _noteOn
pentru a transmite valoarea calculată nodului de câștig:
self.amp.setVolume(_vtov(velocity), self.settings.attack);
Si asta e! Acum, când ne jucăm sintetizatorul, vom observa că volumele variază în funcție de cât de tare apăsăm tastele de pe tastatură.
Activarea rotiței Detune pe tastatura MIDI
Cele mai multe tastaturi MIDI au o roată de acordare; roata vă permite să modificați ușor frecvența notei care este redată în prezent, creând un efect interesant cunoscut sub numele de „detune”. Acest lucru este destul de ușor de implementat pe măsură ce învățați cum să utilizați MIDI, deoarece roata de acordare declanșează și un eveniment MidiMessage cu propriul cod de eveniment (224), pe care îl putem asculta și acționa prin recalcularea valorii frecvenței și actualizarea oscilatorului.
În primul rând, trebuie să surprindem evenimentul în sintezatorul nostru. Pentru a face acest lucru, adăugăm un caz suplimentar la instrucțiunea switch pe care am creat-o în callback-ul _onmidimessage
:
case 224: // the detune value is the third argument of the MidiEvent.data array Engine.detune(e.data[2]); break;
Apoi, definim metoda de detune
pe motorul 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; } } }
Valoarea implicită de detune este 64, ceea ce înseamnă că nu este aplicată nicio detune, așa că în acest caz pur și simplu trecem frecvența curentă oscilatorului.
În cele din urmă, trebuie să actualizăm și metoda _noteOff
, pentru a lua în considerare detune-ul în cazul în care o altă notă este pusă în coadă:
self.osc1.setFrequency(self.currentFreq + self.detuneAmount, self.settings.portamento);
Crearea interfeței
Până acum, am creat doar o casetă de selectare pentru a putea selecta dispozitivul nostru MIDI și un vizualizator de formă de undă, dar nu avem posibilitatea de a modifica sunetul direct interacționând cu pagina web. Să creăm o interfață foarte simplă folosind elemente de formular comune și să le legăm la motorul nostru audio.
Crearea unui aspect pentru interfață
Vom crea diverse elemente de formă pentru a controla sunetul sintetizatorului nostru:
- Un grup radio pentru a selecta tipul de oscilator
- O casetă de selectare pentru a activa/dezactiva filtrul
- Un grup radio pentru a selecta tipul de filtru
- Două game pentru a controla frecvența și rezonanța filtrului
- Două game pentru a controla atacul și eliberarea nodului de câștig
Creând un document HTML pentru interfața noastră, ar trebui să ajungem la ceva de genul acesta:
<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>
Decorarea interfeței cu utilizatorul pentru a arăta elegant nu este ceva pe care îl voi acoperi în acest tutorial MIDI de bază; în schimb, îl putem salva ca un exercițiu pentru a șlefui mai târziu interfața cu utilizatorul, poate pentru a arăta cam așa:
Legarea interfeței la motorul audio
Ar trebui să definim câteva metode pentru a lega aceste comenzi la motorul nostru audio.
Controlul oscilatorului
Pentru oscilator, avem nevoie doar de o metodă care să ne permită să setăm tipul de oscilator:
Oscillator.prototype.setOscType = function(type) { if(type) { self.osc.type = type; } }
Controlul filtrului
Pentru filtru avem nevoie de trei comenzi: unul pentru tipul de filtru, unul pentru frecventa si unul pentru rezonanta. De asemenea, putem conecta metodele _connectFilter
și _disconnectFilter
la valoarea casetei de selectare.
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; } }
Controlul atacului și rezonanței
Pentru a ne modela puțin sunetul, putem schimba parametrii de atac și eliberare ai nodului de câștig. Avem nevoie de două metode pentru aceasta:
function _setAttack(a) { if(a) { self.settings.attack = a / 1000; } } function _setRelease(r) { if(r) { self.settings.release = r / 1000; } }
Configurarea observatorilor
În cele din urmă, în controlerul aplicației noastre, trebuie doar să setăm câțiva observatori și să le legăm de diferitele metode pe care tocmai le-am creat:
$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);
Concluzie
O mulțime de concepte au fost acoperite în acest tutorial MIDI; în cea mai mare parte, am descoperit cum să folosim API-ul WebMIDI, care este destul de nedocumentat în afară de specificația oficială de la W3C. Implementarea Google Chrome este destul de simplă, deși trecerea la un obiect iterator pentru dispozitivele de intrare și ieșire necesită puțină refactorizare pentru codul moștenit folosind implementarea veche.
În ceea ce privește API-ul WebAudio, acesta este un API foarte bogat și am acoperit doar câteva dintre capabilitățile sale în acest tutorial. Spre deosebire de API-ul WebMIDI, API-ul WebAudio este foarte bine documentat, în special pe Mozilla Developer Network. Mozilla Developer Network conține o multitudine de exemple de cod și liste detaliate ale diferitelor argumente și evenimente pentru fiecare componentă, care vă vor ajuta să implementați propriile aplicații audio personalizate bazate pe browser.
Pe măsură ce ambele API-uri continuă să crească, vor deschide câteva posibilități foarte interesante pentru dezvoltatorii JavaScript; permițându-ne să dezvoltăm DAW cu funcții complete, bazate pe browser, care vor putea concura cu echivalentele lor Flash. Și pentru dezvoltatorii de desktop, puteți începe, de asemenea, să vă creați propriile aplicații multiplatformă folosind instrumente precum node-webkit. Sperăm că acest lucru va genera o nouă generație de instrumente muzicale pentru audiofili, care vor da putere utilizatorilor prin reducerea decalajului dintre lumea fizică și cloud.