Comment nous avons créé un son de qualité
Introduction
JAM with Chrome est un projet musical Web créé par Google. JAM with Chrome permet à des utilisateurs du monde entier de former un groupe et de jouer en temps réel dans le navigateur. DinahMoe a eu le grand plaisir de participer à ce projet. Notre rôle était de produire de la musique pour l'application, et de concevoir et développer le composant musical. Le développement s'est articulé autour de trois axes principaux: une station de travail musicale comprenant la lecture MIDI, des samplers logiciels, des effets audio, le routage et le mixage ; un moteur de logique musicale permettant de contrôler la musique de manière interactive en temps réel ; et un composant de synchronisation qui s'assure que tous les joueurs d'une session entendent la musique exactement au même moment, ce qui est indispensable pour pouvoir jouer ensemble.
Pour obtenir le niveau d'authenticité, de précision et de qualité audio le plus élevé possible, nous avons choisi d'utiliser l'API Web Audio. Cette étude de cas présente certains des défis auxquels nous avons été confrontés et la façon dont nous les avons résolus. HTML5Rocks propose déjà de nombreux articles d'introduction à Web Audio. Nous allons donc passer directement aux choses sérieuses.
Écrire des effets audio personnalisés
La spécification de l'API Web Audio inclut un certain nombre d'effets utiles, mais nous avions besoin d'effets plus élaborés pour nos instruments dans JAM avec Chrome. Par exemple, Web Audio propose un nœud de délai natif, mais il existe de nombreux types de retards : retard stéréo, retard ping-pong, retard slapback, etc. Heureusement, il est possible de créer tous ces éléments dans Web Audio à l'aide des nœuds d'effets natifs et d'un peu d'imagination.
Comme nous voulions pouvoir utiliser les nœuds natifs et nos propres effets personnalisés de la manière la plus transparente possible, nous avons décidé de créer un format de wrapper qui permettrait de le faire. Les nœuds natifs de Web Audio utilisent leur méthode de connexion pour associer des nœuds. Nous avons donc dû émuler ce comportement. Voici l'idée de base:
var MyCustomNode = function(){
this.input = audioContext.createGain();
var output = audioContext.createGain();
this.connect = function(target){
output.connect(target);
};
};
Avec ce modèle, nous sommes très proches des nœuds natifs. Voyons comment cela fonctionne.
//create a couple of native nodes and our custom node
var gain = audioContext.createGain(),
customNode = new MyCustomNode(),
anotherGain = audioContext.createGain();
//connect our custom node to the native nodes and send to the output
gain.connect(customNode.input);
customNode.connect(anotherGain);
anotherGain.connect(audioContext.destination);
La seule différence entre notre nœud personnalisé et un nœud natif est que nous devons nous connecter à la propriété d'entrée des nœuds personnalisés. Je suis sûr qu'il existe des moyens de contourner ce problème, mais cela nous suffisait pour nos besoins. Ce modèle peut être développé pour simuler les méthodes de déconnexion des AudioNodes natifs, ainsi que pour prendre en charge les entrées/sorties définies par l'utilisateur lors de la connexion, etc. Consultez les spécifications pour découvrir les fonctionnalités des nœuds natifs.
Maintenant que nous avons notre modèle de base pour créer des effets personnalisés, l'étape suivante consiste à donner au nœud personnalisé un comportement personnalisé. Examinons un nœud de délai slapback.
Répondez avec conviction
Le délai slapback, parfois appelé "écho slapback", est un effet classique utilisé sur un certain nombre d'instruments, des voix de style années 50 aux guitares surf. L'effet prend le son entrant et en joue une copie avec un léger retard d'environ 75 à 250 millisecondes. Cela donne l'impression que le son est renvoyé en arrière, d'où le nom. Pour créer cet effet, procédez comme suit:
var SlapbackDelayNode = function(){
//create the nodes we'll use
this.input = audioContext.createGain();
var output = audioContext.createGain(),
delay = audioContext.createDelay(),
feedback = audioContext.createGain(),
wetLevel = audioContext.createGain();
//set some decent values
delay.delayTime.value = 0.15; //150 ms delay
feedback.gain.value = 0.25;
wetLevel.gain.value = 0.25;
//set up the routing
this.input.connect(delay);
this.input.connect(output);
delay.connect(feedback);
delay.connect(wetLevel);
feedback.connect(delay);
wetLevel.connect(output);
this.connect = function(target){
output.connect(target);
};
};
Comme certains d'entre vous l'ont peut-être déjà compris, ce délai peut également être utilisé avec des temps de retard plus longs, et devenir ainsi un délai mono régulier avec rétroaction. Voici un exemple d'utilisation de ce délai pour vous permettre d'entendre ce à quoi il ressemble.
Routage de l'audio
Lorsque vous travaillez avec différents instruments et parties musicales dans des applications audio professionnelles, il est essentiel de disposer d'un système de routage flexible qui vous permet de mixer et de moduler les sons de manière efficace. Dans JAM with Chrome, nous avons développé un système de bus audio, semblable à celui que l'on trouve dans les tables de mixage physiques. Cela nous permet de connecter tous les instruments qui ont besoin d'un effet de réverbération à un bus ou à un canal commun, puis d'ajouter la réverbération à ce bus au lieu d'en ajouter une à chaque instrument. Il s'agit d'une optimisation majeure, et il est vivement recommandé de procéder de la même manière dès que vous commencez à créer des applications plus complexes.
Heureusement, c'est très facile à réaliser dans Web Audio. Nous pouvons utiliser le squelette que nous avons défini pour les effets et l'utiliser de la même manière.
var AudioBus = function(){
this.input = audioContext.createGain();
var output = audioContext.createGain();
//create effect nodes (Convolver and Equalizer are other custom effects from the library presented at the end of the article)
var delay = new SlapbackDelayNode(),
convolver = new tuna.Convolver(),
equalizer = new tuna.Equalizer();
//route 'em
//equalizer -> delay -> convolver
this.input.connect(equalizer);
equalizer.connect(delay.input);
delay.connect(convolver);
convolver.connect(output);
this.connect = function(target){
output.connect(target);
};
};
Il se présente comme suit:
//create some native oscillators and our custom audio bus
var bus = new AudioBus(),
instrument1 = audioContext.createOscillator(),
instrument2 = audioContext.createOscillator(),
instrument3 = audioContext.createOscillator();
//connect our instruments to the same bus
instrument1.connect(bus.input);
instrument2.connect(bus.input);
instrument3.connect(bus.input);
bus.connect(audioContext.destination);
Et voilà, nous avons appliqué un délai, une égalisation et une réverbération (qui est un effet plutôt coûteux en termes de performances) à moitié prix, comme si nous avions appliqué les effets à chaque instrument séparément. Si vous souhaitez ajouter un peu de piquant au bus, vous pouvez ajouter deux nouveaux nœuds de gain (preGain et postGain), ce qui vous permettra de couper ou d'atténuer les sons d'un bus de deux manières différentes. Le prégain est placé avant les effets, et le postgain à la fin de la chaîne. Si nous atténuons ensuite le préGain, les effets continueront de résonner après que le gain aura atteint son point le plus bas, mais si nous atténuons le postGain, tous les sons seront coupés en même temps.
Où allons-nous à partir d'ici ?
Ces méthodes que j'ai décrites ici peuvent et doivent être développées. Des éléments tels que l'entrée et la sortie des nœuds personnalisés, ainsi que les méthodes de connexion, peuvent/devraient être implémentés à l'aide de l'héritage basé sur un prototype. Les bus doivent pouvoir créer des effets de manière dynamique en transmettant une liste d'effets.
Pour célébrer le lancement de JAM avec Chrome, nous avons décidé de rendre notre framework d'effets Open Source. Si cette brève introduction vous a plu, n'hésitez pas à y jeter un œil et à y contribuer. Cliquez ici pour en savoir plus sur la standardisation d'un format pour les entités Web Audio personnalisées. Impliquez-vous !