Didacticiel MIDI : Création d'applications audio basées sur un navigateur et contrôlées par du matériel MIDI
Publié: 2022-03-11Alors que l'API Web Audio gagne en popularité, en particulier parmi les développeurs de jeux HTML5, l'API Web MIDI est encore peu connue des développeurs frontaux. Une grande partie de cela est probablement liée à son manque actuel de support et de documentation accessible ; l'API Web MIDI n'est actuellement prise en charge que dans Google Chrome, à condition que vous activiez un indicateur spécial pour celle-ci. Les fabricants de navigateurs accordent actuellement peu d'importance à cette API, car il est prévu qu'elle fasse partie de la norme ES7.
Conçu au début des années 80 par plusieurs représentants de l'industrie musicale, MIDI (abréviation de Musical Instrument Digital Interface) est un protocole de communication standard pour les appareils de musique électronique. Même si d'autres protocoles, comme OSC, ont été développés depuis ; trente ans plus tard, le MIDI est toujours le protocole de communication de facto pour les fabricants de matériel audio. Vous aurez du mal à trouver un producteur de musique moderne qui ne possède pas au moins un appareil MIDI dans son studio.
Avec le développement rapide et l'adoption de l'API Web Audio, nous pouvons maintenant commencer à créer des applications basées sur un navigateur qui comblent le fossé entre le cloud et le monde physique. Non seulement l'API Web MIDI nous permet de créer des synthétiseurs et des effets audio, mais nous pouvons même commencer à créer des DAW (Digital Audio Workstation) basés sur un navigateur, similaires en termes de fonctionnalités et de performances à leurs homologues actuels basés sur Flash (consultez Audiotool, par exemple ).
Dans ce didacticiel MIDI, je vais vous guider à travers les bases de l'API Web MIDI, et nous allons construire un synthé mono simple que vous pourrez jouer avec votre appareil MIDI préféré. Le code source complet est disponible ici, et vous pouvez tester directement la démo en direct. Si vous ne possédez pas de périphérique MIDI, vous pouvez toujours suivre ce didacticiel en consultant la branche "clavier" du référentiel GitHub, qui permet une prise en charge de base de votre clavier d'ordinateur, afin que vous puissiez jouer des notes et changer d'octave. C'est également la version qui est disponible en tant que démo en direct. Cependant, en raison des limitations du matériel informatique, la vélocité et le désaccord sont tous deux désactivés chaque fois que vous utilisez le clavier de votre ordinateur pour contrôler le synthétiseur. Veuillez vous référer au fichier readme sur GitHub pour en savoir plus sur le mappage clé/note.
Prérequis du Tutoriel Midi
Vous aurez besoin des éléments suivants pour ce didacticiel MIDI :
- Google Chrome (version 38 ou supérieure) avec le drapeau
#enable-web-midi
activé - (Facultatif) Un appareil MIDI, qui peut déclencher des notes, connecté à votre ordinateur
Nous utiliserons également Angular.js pour apporter un peu de structure à notre application ; par conséquent, une connaissance de base du cadre est une condition préalable.
Commencer
Nous allons modulariser notre application MIDI de A à Z en la séparant en 3 modules :
- WebMIDI : gestion des différents appareils MIDI connectés à votre ordinateur
- WebAudio : fournir la source audio de notre synthé
- WebSynth : connecter l'interface web au moteur audio
Un module d' App
gérera l'interaction de l'utilisateur avec l'interface utilisateur Web. Notre structure d'application pourrait ressembler un peu à ceci :
|- app |-- js |--- midi.js |--- audio.js |--- synth.js |--- app.js |- index.html
Vous devez également installer les bibliothèques suivantes pour vous aider à créer votre application : Angular.js, Bootstrap et jQuery. Le moyen le plus simple de les installer est probablement via Bower.
Le module WebMIDI : connexion avec le monde réel
Commençons par comprendre comment utiliser le MIDI en connectant nos appareils MIDI à notre application. Pour ce faire, nous allons créer une usine simple renvoyant une seule méthode. Pour se connecter à nos appareils MIDI via l'API Web MIDI, nous devons appeler la méthode 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 }; }]);
Et c'est à peu près tout!
La méthode requestMIDIAccess
renvoie une promesse, nous pouvons donc simplement la renvoyer directement et gérer le résultat de la promesse dans le contrôleur de notre application :
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); }); }]);
Comme mentionné, la méthode requestMIDIAccess
renvoie une promesse, en passant un objet à la méthode then
, avec deux propriétés : entrées et sorties.
Dans les versions antérieures de Chrome, ces deux propriétés étaient des méthodes permettant de récupérer directement un tableau de périphériques d'entrée et de sortie. Cependant, dans les dernières mises à jour, ces propriétés sont désormais des objets. Cela fait une grande différence, puisque nous devons maintenant appeler la méthode values
sur l'objet input ou outputs pour récupérer la liste correspondante des appareils. Cette méthode agit comme une fonction génératrice et renvoie un itérateur. Encore une fois, cette API est censée faire partie d'ES7 ; par conséquent, l'implémentation d'un comportement de type générateur est logique, même si elle n'est pas aussi simple que l'implémentation d'origine.
Enfin, nous pouvons récupérer le nombre de périphériques via la propriété size
de l'objet itérateur. S'il y a au moins un périphérique, nous parcourons simplement le résultat en appelant la méthode next
de l'objet itérateur et en poussant chaque périphérique vers un tableau défini sur le $scope. Sur le front-end, nous pouvons implémenter une simple boîte de sélection qui répertorie tous les périphériques d'entrée disponibles et nous permet de choisir le périphérique que nous voulons utiliser comme périphérique actif pour contrôler le synthé 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>
Nous avons lié cette boîte de sélection à une variable $scope appelée activeDevice
que nous utiliserons plus tard pour connecter ce périphérique actif au synthé.
Le module WebAudio : Faire du bruit
L'API WebAudio nous permet non seulement de lire des fichiers audio, mais également de générer des sons en recréant les composants essentiels des synthétiseurs tels que les oscillateurs, les filtres et les nœuds de gain, entre autres.
Créer un oscillateur
Le rôle des oscillateurs est de produire une forme d'onde. Il existe différents types de formes d'onde, parmi lesquelles quatre sont prises en charge dans l'API WebAudio : sinusoïdale, carrée, triangulaire et en dents de scie. On dit que les formes d'onde "oscillent" à une certaine fréquence, mais il est également possible de définir sa propre table d'onde personnalisée si nécessaire. Une certaine gamme de fréquences est audible par les êtres humains - on les appelle des sons. Alternativement, lorsqu'ils oscillent à basses fréquences, les oscillateurs peuvent également nous aider à construire des LFO ("oscillateur basse fréquence") afin que nous puissions moduler nos sons (mais cela dépasse le cadre de ce didacticiel).
La première chose que nous devons faire pour créer du son est d'instancier un nouveau AudioContext
:
function _createContext() { self.ctx = new $window.AudioContext(); }
À partir de là, nous pouvons instancier n'importe lequel des composants mis à disposition par l'API WebAudio. Étant donné que nous pouvons créer plusieurs instances de chaque composant, il est logique de créer des services pour pouvoir créer de nouvelles instances uniques des composants dont nous avons besoin. Commençons par créer le service pour générer un nouvel oscillateur :
angular .module('WebAudio', []) .service('OSC', function() { var self; function Oscillator(ctx) { self = this; self.osc = ctx.createOscillator(); return self; } });
Nous pouvons maintenant instancier de nouveaux oscillateurs à notre guise, en passant en argument l'instance AudioContext que nous avons créée précédemment. Pour faciliter les choses sur la route, nous ajouterons quelques méthodes wrapper - un simple sucre syntaxique - et renverrons la fonction 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;
Créer un filtre multipasse et un contrôle de volume
Nous avons besoin de deux composants supplémentaires pour compléter notre moteur audio de base : un filtre multipasse, pour donner un peu de forme à notre son, et un nœud de gain pour contrôler le volume de notre son et activer et désactiver le volume. Pour ce faire, nous pouvons procéder de la même manière que nous l'avons fait pour l'oscillateur : créer des services renvoyant une fonction avec des méthodes wrapper. Tout ce que nous avons à faire est de fournir l'instance AudioContext et d'appeler la méthode appropriée.
Nous créons un filtre en appelant la méthode createBiquadFilter
de l'instance AudioContext :
ctx.createBiquadFilter();
De même, pour un nœud de gain, nous appelons la méthode createGain
:
ctx.createGain();
Le module WebSynth : câbler les choses
Nous sommes maintenant presque prêts à construire notre interface de synthé et à connecter des appareils MIDI à notre source audio. Tout d'abord, nous devons connecter notre moteur audio ensemble et le préparer à recevoir des notes MIDI. Pour connecter le moteur audio, nous créons simplement de nouvelles instances des composants dont nous avons besoin, puis les «connectons» ensemble en utilisant la méthode de connect
disponible pour les instances de chaque composant. La méthode connect
prend un argument, qui est simplement le composant auquel vous voulez connecter l'instance actuelle. Il est possible d'orchestrer une chaîne de composants plus élaborée car la méthode de connect
peut connecter un nœud à plusieurs modulateurs (ce qui permet d'implémenter des choses comme le fondu enchaîné et plus encore).
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); }
Nous venons de construire le câblage interne de notre moteur audio. Vous pouvez jouer un peu et essayer différentes combinaisons de câblage, mais n'oubliez pas de baisser le volume pour éviter de devenir sourd. Nous pouvons maintenant connecter l'interface MIDI à notre application et envoyer des messages MIDI au moteur audio. Nous allons configurer un observateur sur la boîte de sélection de l'appareil pour le "brancher" virtuellement sur notre synthé. Nous allons alors écouter les messages MIDI provenant de l'appareil, et transmettre les informations au moteur 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; }
Ici, nous écoutons les événements MIDI de l'appareil, analysons les données de l'objet MidiEvent et les transmettons à la méthode appropriée ; noteOn
ou noteOff
, en fonction du code d'événement (144 pour noteOn, 128 pour noteOff). Nous pouvons maintenant ajouter la logique dans les méthodes respectives du module audio pour réellement générer un son :
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); } }
Quelques choses se passent ici. Dans la méthode noteOn
, nous poussons d'abord la note actuelle vers un tableau de notes. Même si nous construisons un synthé mono (ce qui signifie que nous ne pouvons jouer qu'une seule note à la fois), nous pouvons toujours avoir plusieurs doigts à la fois sur le clavier. Donc, nous devons mettre toutes ces notes en file d'attente afin que lorsque nous relâchons une note, la suivante soit jouée. Nous devons ensuite arrêter l'oscillateur pour assigner la nouvelle fréquence, que nous convertissons d'une note MIDI (échelle de 0 à 127) en une valeur de fréquence réelle avec un peu de maths :
function _mtof(note) { return 440 * Math.pow(2, (note - 69) / 12); }
Dans la méthode noteOff
, nous commençons par rechercher la note dans le tableau des notes actives et la supprimer. Ensuite, si c'était la seule note du tableau, nous coupons simplement le volume.

Le deuxième argument de la méthode setVolume
est le temps de transition, c'est-à-dire le temps qu'il faut au gain pour atteindre la nouvelle valeur de volume. En termes musicaux, si la note est allumée, ce serait l'équivalent du temps d'attaque, et si la note est éteinte, c'est l'équivalent du temps de relâchement.
Le module WebAnalyser : Visualiser notre son
Une autre fonctionnalité intéressante que nous pouvons ajouter à notre synthé est un nœud analyseur, qui nous permet d'afficher la forme d'onde de notre son en utilisant canvas pour le rendre. La création d'un nœud d'analyseur est un peu plus compliquée que d'autres objets AudioContext, car elle nécessite également de créer un nœud scriptProcessor pour effectuer réellement l'analyse. Nous commençons par sélectionner l'élément canvas sur le 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; }
Ensuite, nous ajoutons une méthode connect
, dans laquelle nous créerons à la fois l'analyseur et le processeur 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); } };
Tout d'abord, nous créons un objet scriptProcessor et le connectons à la destination. Ensuite, nous créons l'analyseur lui-même, que nous alimentons avec la sortie audio de l'oscillateur ou du filtre. Remarquez comment nous devons encore connecter la sortie audio à la destination pour pouvoir l'entendre ! Nous devons également définir les couleurs du dégradé de notre graphique - cela se fait en appelant la méthode createLinearGradient
de l'élément canvas.
Enfin, le scriptProcessor déclenchera un événement 'audioprocess' sur un intervalle ; lorsque cet événement est déclenché, nous calculons les fréquences moyennes capturées par l'analyseur, vidons le canevas et redessinons le nouveau graphique de fréquence en appelant la méthode 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)); } }
Enfin et surtout, nous devrons modifier un peu le câblage de notre moteur audio pour accueillir ce nouveau composant :
// 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); }
Nous avons maintenant un joli visualiseur qui nous permet d'afficher la forme d'onde de notre synthé en temps réel ! Cela implique un peu de travail à configurer, mais c'est très intéressant et perspicace, surtout lors de l'utilisation de filtres.
Développer notre synthé : ajouter de la vélocité et du désaccord
À ce stade de notre didacticiel MIDI, nous avons un synthé plutôt cool - mais il joue chaque note au même volume. En effet, au lieu de gérer correctement les données de vélocité, nous réglons simplement le volume sur une valeur fixe de 1,0. Commençons par corriger cela, puis nous verrons comment activer la molette de désaccord que vous trouvez sur la plupart des claviers MIDI courants.
Activation de la vélocité
Si vous ne la connaissez pas, la "vitesse" est liée à la force avec laquelle vous appuyez sur la touche de votre clavier. Sur la base de cette valeur, le son créé semble plus doux ou plus fort.
Dans notre synthé didacticiel MIDI, nous pouvons émuler ce comportement en jouant simplement avec le volume du nœud de gain. Pour ce faire, nous devons d'abord faire un peu de calcul pour convertir les données MIDI en une valeur flottante comprise entre 0,0 et 1,0 pour passer au nœud de gain :
function _vtov (velocity) { return (velocity / 127).toFixed(2); }
La plage de vélocité d'un appareil MIDI est de 0 à 127, donc nous divisons simplement cette valeur par 127 et renvoyons une valeur flottante avec deux décimales. Ensuite, nous pouvons mettre à jour la méthode _noteOn
pour transmettre la valeur calculée au nœud de gain :
self.amp.setVolume(_vtov(velocity), self.settings.attack);
Et c'est tout! Maintenant, lorsque nous jouons sur notre synthé, nous remarquons que les volumes varient en fonction de la force avec laquelle nous appuyons sur les touches de notre clavier.
Activer la molette Detune sur votre clavier MIDI
La plupart des claviers MIDI disposent d'une molette de désaccord ; la molette vous permet de modifier légèrement la fréquence de la note en cours de lecture, créant un effet intéressant appelé "detune". Ceci est assez facile à mettre en œuvre lorsque vous apprenez à utiliser le MIDI, car la molette de désaccord déclenche également un événement MidiMessage avec son propre code d'événement (224), que nous pouvons écouter et agir en recalculant la valeur de fréquence et en mettant à jour l'oscillateur.
Tout d'abord, nous devons capturer l'événement dans notre synthé. Pour ce faire, nous ajoutons un cas supplémentaire à l'instruction switch que nous avons créée dans le rappel _onmidimessage
:
case 224: // the detune value is the third argument of the MidiEvent.data array Engine.detune(e.data[2]); break;
Ensuite, nous définissons la méthode detune
sur le moteur 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; } } }
La valeur de désaccord par défaut est 64, ce qui signifie qu'aucun désaccord n'est appliqué, donc dans ce cas, nous passons simplement la fréquence actuelle à l'oscillateur.
Enfin, nous devons également mettre à jour la méthode _noteOff
, pour prendre en compte le désaccord au cas où une autre note serait mise en file d'attente :
self.osc1.setFrequency(self.currentFreq + self.detuneAmount, self.settings.portamento);
Création de l'interface
Jusqu'à présent, nous n'avons créé qu'une boîte de sélection pour pouvoir sélectionner notre appareil MIDI et un visualiseur de forme d'onde, mais nous n'avons aucune possibilité de modifier le son directement en interagissant avec la page Web. Créons une interface très simple en utilisant des éléments de formulaire communs et lions-les à notre moteur audio.
Création d'une mise en page pour l'interface
Nous allons créer divers éléments de forme pour contrôler le son de notre synthé :
- Un groupe radio pour sélectionner le type d'oscillateur
- Une case à cocher pour activer/désactiver le filtre
- Un groupe radio pour sélectionner le type de filtre
- Deux gammes pour contrôler la fréquence et la résonance du filtre
- Deux gammes pour contrôler l'attaque et la libération du nœud de gain
En créant un document HTML pour notre interface, nous devrions nous retrouver avec quelque chose comme ceci :
<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>
Décorer l'interface utilisateur pour qu'elle ait l'air fantaisiste n'est pas quelque chose que je couvrirai dans ce didacticiel MIDI de base ; à la place, nous pouvons l'enregistrer en tant qu'exercice pour plus tard afin de peaufiner l'interface utilisateur, peut-être pour ressembler à ceci :
Liaison de l'interface au moteur audio
Nous devons définir quelques méthodes pour lier ces contrôles à notre moteur audio.
Contrôle de l'oscillateur
Pour l'oscillateur, nous avons seulement besoin d'une méthode nous permettant de définir le type d'oscillateur :
Oscillator.prototype.setOscType = function(type) { if(type) { self.osc.type = type; } }
Contrôle du filtre
Pour le filtre, nous avons besoin de trois contrôles : un pour le type de filtre, un pour la fréquence et un pour la résonance. Nous pouvons également connecter les méthodes _connectFilter
et _disconnectFilter
à la valeur de la case à cocher.
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; } }
Contrôler l'attaque et la résonance
Pour façonner un peu notre son, nous pouvons modifier les paramètres d'attaque et de relâchement du nœud de gain. Nous avons besoin de deux méthodes pour cela :
function _setAttack(a) { if(a) { self.settings.attack = a / 1000; } } function _setRelease(r) { if(r) { self.settings.release = r / 1000; } }
Configuration des observateurs
Enfin, dans le contrôleur de notre application, il nous suffit de configurer quelques observateurs et de les lier aux différentes méthodes que nous venons de créer :
$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);
Conclusion
De nombreux concepts ont été abordés dans ce didacticiel MIDI ; surtout, nous avons découvert comment utiliser l'API WebMIDI, qui est assez peu documentée en dehors de la spécification officielle du W3C. L'implémentation de Google Chrome est assez simple, bien que le passage à un objet itérateur pour les périphériques d'entrée et de sortie nécessite un peu de refactorisation pour le code hérité utilisant l'ancienne implémentation.
Quant à l'API WebAudio, il s'agit d'une API très riche, et nous n'avons couvert que quelques-unes de ses fonctionnalités dans ce didacticiel. Contrairement à l'API WebMIDI, l'API WebAudio est très bien documentée, notamment sur le Mozilla Developer Network. Le réseau de développeurs Mozilla contient une pléthore d'exemples de code et des listes détaillées des divers arguments et événements pour chaque composant, ce qui vous aidera à implémenter vos propres applications audio personnalisées basées sur un navigateur.
Au fur et à mesure que les deux API continueront à se développer, cela ouvrira des possibilités très intéressantes pour les développeurs JavaScript ; nous permettant de développer une DAW complète, basée sur un navigateur, qui pourra rivaliser avec leurs équivalents Flash. Et pour les développeurs de bureau, vous pouvez également commencer à créer vos propres applications multiplateformes à l'aide d'outils tels que node-webkit. Espérons que cela engendrera une nouvelle génération d'outils musicaux pour les audiophiles qui permettront aux utilisateurs de combler le fossé entre le monde physique et le cloud.