Étude de cas : JAM with Chrome

Comment nous avons rendu l'audio rock

Oskar Eriksson
Oskar Eriksson

Présentation

JAM avec Chrome est un projet musical Web créé par Google. JAM with Chrome permet à des personnes du monde entier de former un groupe et de jouer en temps réel dans leur navigateur. Chez DinahMoe, nous avons eu le plaisir de participer à ce projet. Notre rôle consistait à produire de la musique pour l'application, puis à concevoir et développer la partie musicale. Le développement comprenait trois domaines principaux: un "poste de travail musical" incluant la lecture MIDI, des échantillonneurs logiciels, des effets audio, le routage et le mixage, un moteur logique pour contrôler la musique de façon interactive et en temps réel, et un composant de synchronisation qui s'assure que tous les joueurs d'une session entendent la musique en même temps, condition préalable pour pouvoir jouer ensemble.

Pour obtenir le plus haut niveau possible d'authenticité, de précision et de qualité audio, nous avons choisi d'utiliser l'API Web Audio. Cette étude de cas abordera certains des défis qui nous ont été présentés et comment nous les avons résolus. Chez HTML5Rocks, il existe déjà un certain nombre d'excellents articles d'introduction pour vous aider à vous lancer avec Web Audio, nous allons donc nous plonger dans le vif du sujet.

Écrire des effets audio personnalisés

Un certain nombre d'effets utiles sont inclus dans la spécification de l'API Web Audio, mais nous avions besoin d'effets plus élaborés pour nos instruments dans JAM with Chrome. Par exemple, il existe un nœud de délai natif dans Web Audio, mais il existe de nombreux types de retards : retard stéréo, délai ping-pong, délai de slapback, etc. Heureusement, il est possible de créer tous ces éléments dans Web Audio en utilisant les nœuds d'effets natifs et un peu d'imagination.

Comme nous voulions être en mesure d'utiliser les nœuds natifs et nos propres effets personnalisés de manière aussi transparente que possible, nous avons décidé de créer un format de wrapper. Les nœuds natifs de Web Audio utilisent sa méthode de connexion pour relier les nœuds entre eux. Nous avons donc dû émuler ce comportement. L'idée de base se présente comme suit:

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 pourrait être utilisé.

//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);
Acheminer le nœud personnalisé

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 c'était suffisamment proche pour nos besoins. Ce modèle peut être développé davantage pour simuler les méthodes de déconnexion des AudioNodes natifs, ainsi que pour s'adapter aux entrées/sorties définies par l'utilisateur lors de la connexion, etc. Consultez la spécification pour voir ce que les nœuds natifs peuvent faire.

Maintenant que nous disposions de notre modèle de base pour créer des effets personnalisés, l'étape suivante consistait à personnaliser le comportement du nœud personnalisé. Examinons un nœud de délai d'ancrage.

Plongez dans l'air

Le décalage, parfois appelé "écho slapback", est un effet classique utilisé sur un certain nombre d'instruments, des voix des années 50 aux guitares de surf. L'effet capte le son entrant et en diffuse une copie avec un léger décalage d'environ 75 à 250 millisecondes. Cela donne l'impression que l'on appuie sur le son, d'où son nom. Nous pouvons créer l'effet comme ceci:

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);
    };
};
Routage interne du nœud d'ancrage

Comme certains d'entre vous l'ont peut-être déjà remarqué, ce délai peut également être utilisé avec des délais plus importants, et ainsi devenir un délai mono standard avec des commentaires. L'exemple ci-dessous utilise ce décalage pour vous permettre de l'entendre.

Routage de l'audio

Lorsque vous travaillez avec différents instruments et pièces de musique 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 avec Chrome, nous avons développé un système de bus audio, semblable à celui des tables de mixage physiques. Cela nous permet d'associer tous les instruments nécessitant un effet de réverbération à un bus ou à un canal commun, puis d'ajouter la réverbération à ce bus au lieu d'ajouter une réverbération à chaque instrument. Il s'agit d'une optimisation majeure, et nous vous recommandons d'effectuer une opération similaire dès que vous commencerez à concevoir des applications plus complexes.

Routage de l'AudioBus

Heureusement, c'est très facile en Web Audio. Nous pouvons essentiellement 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 serait utilisé comme ceci:

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

Voilà, nous avons appliqué le délai, l'égalisation et la réverbération (ce qui est un effet plutôt coûteux en termes de performances) à un coût divisé par deux, comme si nous avions appliqué les effets à chaque instrument. Si nous voulions apporter un peu de piment au bus, nous pourrions ajouter deux nouveaux nœuds de gain, preGain et postGain, qui nous permettraient de désactiver ou d'assouplir les sons dans un bus de deux manières différentes. Le preGain est placé avant les effets, et postGain est placé à la fin de la chaîne. Si nous atténuons ensuite la valeur preGain, les effets continuent de résonner une fois que le gain a atteint la valeur la plus basse, mais si nous la faussons après la prise de gain, tous les sons sont coupés en même temps.

Où voulez-vous aller ?

Les méthodes que j'ai décrites ici peuvent et doivent être développées davantage. Des éléments tels que l'entrée et la sortie des nœuds personnalisés et les méthodes de connexion pourraient/devraient être implémentées en utilisant l'héritage basé sur le prototype. Les bus doivent pouvoir créer des effets de manière dynamique en recevant une liste d'effets.

Pour fêter le lancement de JAM avec Chrome, nous avons décidé de proposer notre framework d'effets Open Source. Si cette brève introduction vous a plu, n'hésitez pas à la regarder et n'hésitez pas à apporter votre contribution. Cliquez ici pour en savoir plus sur la normalisation d'un format pour les entités Web Audio personnalisées. Impliquez-vous !