Fallstudie – JAM mit Chrome

So haben wir den Audiotrack zum Hit gemacht

Oskar Eriksson
Oskar Eriksson

Einführung

JAM with Chrome ist ein webbasiertes Musikprojekt von Google. Mit JAM with Chrome können Nutzer aus aller Welt eine Band gründen und in Echtzeit im Browser jammen. Wir von DinahMoe hatten das große Vergnügen, an diesem Projekt mitzuwirken. Unsere Aufgabe bestand darin, Musik für die App zu produzieren und die Musikkomponente zu entwerfen und zu entwickeln. Die Entwicklung bestand aus drei Hauptbereichen: einem Music Workstation mit MIDI-Wiedergabe, Software-Samplern, Audioeffekten, Routing und Mixing; einer Musiklogik-Engine zur interaktiven Steuerung der Musik in Echtzeit und einer Synchronisierungskomponente, die dafür sorgt, dass alle Spieler in einer Session die Musik genau zur selben Zeit hören, was eine Voraussetzung für das gemeinsame Musizieren ist.

Um für größtmögliche Authentizität, Genauigkeit und Audioqualität zu sorgen, haben wir uns für die Web Audio API entschieden. In dieser Fallstudie werden einige der Herausforderungen beschrieben, denen wir uns gestellt haben, und wie wir sie gelöst haben. Es gibt bereits eine Reihe von guten Einführungsartikeln hier bei HTML5Rocks, die Ihnen den Einstieg in Web Audio erleichtern. Wir springen also direkt ins kalte Wasser.

Benutzerdefinierte Audioeffekte schreiben

Die Web Audio API enthält eine Reihe nützlicher Effekte, aber wir brauchten komplexere Effekte für unsere Instrumente in JAM mit Chrome. In Web Audio gibt es beispielsweise einen nativen Delay-Knoten, aber es gibt viele Arten von Verzögerungen – Stereo-Delay, Ping-Pong-Delay, Slapback-Delay und so weiter. Glücklicherweise können Sie alle diese Effekte in Web Audio mithilfe der nativen Effektknoten und etwas Fantasie erstellen.

Da wir die nativen Knoten und unsere eigenen benutzerdefinierten Effekte so transparent wie möglich verwenden wollten, haben wir uns entschieden, ein Wrapper-Format zu erstellen, das dies ermöglicht. Die nativen Knoten in Web Audio verwenden die connect-Methode, um Knoten miteinander zu verknüpfen. Daher mussten wir dieses Verhalten emulieren. So sieht die Grundidee aus:

var MyCustomNode = function(){
    this.input = audioContext.createGain();
    var output = audioContext.createGain();

    this.connect = function(target){
       output.connect(target);
    };
};

Mit diesem Muster sind wir den nativen Knoten sehr nahe. Sehen wir uns an, wie das funktioniert.

//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);
Benutzerdefinierten Knoten weiterleiten

Der einzige Unterschied zwischen unserem benutzerdefinierten und einem nativen Knoten besteht darin, dass wir eine Verbindung zur Eingabeeigenschaft des benutzerdefinierten Knotens herstellen müssen. Sicherlich gibt es Möglichkeiten, dies zu umgehen, aber das war für unsere Zwecke ausreichend. Dieses Muster kann weiter entwickelt werden, um die Trennen-Methoden von nativen AudioNodes zu simulieren und benutzerdefinierte Eingaben/Ausgaben bei der Verbindung zu berücksichtigen. In der Spezifikation finden Sie weitere Informationen zu den Funktionen der nativen Knoten.

Nachdem wir nun unser grundlegendes Muster für die Erstellung benutzerdefinierter Effekte hatten, ging es im nächsten Schritt darum, dem benutzerdefinierten Knoten ein benutzerdefiniertes Verhalten zuzuweisen. Sehen wir uns einen Slapback-Delay-Knoten an.

Schlag zurück, als ob es um dein Leben ginge

Die Slapback-Verzögerung, manchmal auch Slapback-Echo genannt, ist ein klassischer Effekt, der auf einer Reihe von Instrumenten verwendet wird, von 50er-Jahre-Gesang bis hin zu Surfgitarren. Der Effekt nimmt den eingehenden Ton auf und spielt eine Kopie des Tons mit einer leichten Verzögerung von etwa 75 bis 250 Millisekunden ab. Das gibt dem Klang das Gefühl, zurückgeschlagen zu werden, daher der Name. So können wir den Effekt erstellen:

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);
    };
};
Internes Routing des Slapback-Knotens

Wie einige von euch vielleicht schon erkannt haben, kann diese Verzögerung auch mit größeren Verzögerungszeiten verwendet werden und so zu einer regulären Mono-Verzögerung mit Feedback werden. Hier ist ein Beispiel für diese Verzögerung, damit Sie hören können, wie sie klingt.

Audio weiterleiten

Wenn Sie in professionellen Audioanwendungen mit verschiedenen Instrumenten und musikalischen Elementen arbeiten, ist ein flexibles Routingsystem unerlässlich, mit dem Sie die Töne effektiv mischen und modulieren können. In JAM with Chrome haben wir ein Audiobussystem entwickelt, das den physischen Mischpulten ähnelt. So können wir alle Instrumente, die einen Halleffekt benötigen, an einen gemeinsamen Bus oder Kanal anschließen und dann den Halleffekt diesem Bus hinzufügen, anstatt jedem einzelnen Instrument einen Halleffekt hinzuzufügen. Dies ist eine wichtige Optimierung und es wird empfohlen, etwas Ähnliches zu tun, sobald Sie mit komplexeren Anwendungen beginnen.

Routing des AudioBus

Glücklicherweise ist das in Web Audio ganz einfach. Wir können das für die Effekte definierte Skelett im Grunde auf die gleiche Weise verwenden.

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

Die Verwendung erfolgt so:

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

Und voila, wir haben Delay, Equalizer und Reverb (ein ziemlich kostspieliger Effekt in Bezug auf die Leistung) zu einem halben Preis angewendet, als hätten wir die Effekte auf jedes einzelne Instrument angewendet. Wenn wir dem Bus noch etwas mehr Würze verleihen möchten, könnten wir zwei neue Gain-Knoten hinzufügen: preGain und postGain. Damit könnten wir die Töne in einem Bus auf zwei verschiedene Arten deaktivieren oder ausblenden. PreGain wird vor den Effekten und PostGain am Ende der Kette platziert. Wenn wir dann den PreGain ausblenden, klingen die Effekte auch nach dem Erreichen des Minimums noch nach. Wenn wir jedoch den PostGain ausblenden, wird der gesamte Ton gleichzeitig stummgeschaltet.

Wie geht es weiter?

Diese hier beschriebenen Methoden können und sollten weiterentwickelt werden. Dinge wie die Eingabe und Ausgabe der benutzerdefinierten Knoten und die Verbindungsmethoden könnten/sollten mithilfe der prototypbasierten Vererbung implementiert werden. Busse sollten in der Lage sein, Effekte dynamisch zu erstellen, indem eine Liste von Effekten übergeben wird.

Zum Release von JAM mit Chrome haben wir uns entschieden, unser Framework für Effekte Open Source zu machen. Wenn Sie neugierig geworden sind, können Sie sich die Seite ansehen und uns gerne mit Ihren Beiträgen unterstützen. Hier findet eine Diskussion zur Standardisierung eines Formats für benutzerdefinierte Web Audio-Entitäten statt. Mach mit!