Fallstudie – JAM mit Chrome

So haben wir den Soundtrack gemacht

Oskar Eriksson
Oskar Eriksson

Einleitung

JAM mit Chrome ist ein webbasiertes Musikprojekt von Google. Dank JAM mit Chrome können Nutzer aus aller Welt eine Band gründen und in Echtzeit im Browser musizieren. Wir bei DinahMoe hatten großes Vergnügen, ein Teil dieses Projekts zu sein. Unsere Rolle bestand darin, Musik für die App zu produzieren und die Musikkomponente zu entwerfen und zu entwickeln. Die Entwicklung bestand aus drei Hauptbereichen: einer „Musik-Workstation“ mit Midi-Wiedergabe, Software-Samplern, Audioeffekten, Routing und Mixing, einer Musiklogik-Engine, die die Musik interaktiv in Echtzeit steuert, und einer Synchronisierungskomponente, die dafür sorgt, dass alle Spieler in einer Sitzung die Musik genau zur gleichen Zeit hören – eine Voraussetzung dafür, dass sie zusammen spielen können.

Um ein Höchstmaß an Authentizität, Genauigkeit und Audioqualität zu erreichen, haben wir uns für die Web Audio API entschieden. In dieser Fallstudie werden einige der Herausforderungen, mit denen wir konfrontiert wurden, und wie wir sie gelöst haben, diskutiert. Auf HTML5Rocks gibt es bereits eine Reihe von tollen Einführungsartikeln, die Ihnen den Einstieg in Web Audio erleichtern.

Benutzerdefinierte Audioeffekte schreiben

Das Web Audio API bietet eine Reihe nützlicher Effekte, die in der Spezifikation enthalten sind, aber wir benötigten komplexere Effekte für unsere Instrumente in JAM mit Chrome. Es gibt beispielsweise einen nativen Verzögerungsknoten in Web Audio, aber es gibt viele Arten von Verzögerungen – Stereo-, Ping-Pong- oder Slapback-Verzögerung. All dies ist mit etwas Fantasie und mithilfe der nativen Effektknoten in Web Audio möglich.

Da wir die nativen Knoten und unsere eigenen benutzerdefinierten Effekte so transparent wie möglich nutzen wollten, beschlossen wir, ein hierfür geeignetes Wrapper-Format zu erstellen. Die nativen Knoten in Web Audio verwenden ihre Verbindungsmethode, um Knoten miteinander zu verbinden, sodass wir dieses Verhalten emulieren mussten. 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 sehr nah an den nativen Knoten. 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 zum Eingabeattribut für benutzerdefinierte Knoten herstellen müssen. Ich bin sicher, es gibt Möglichkeiten, dies zu umgehen, aber dies war für unsere Zwecke nahe genug. Dieses Muster lässt sich weiterentwickeln, um die Methoden zum Trennen nativer Audioknoten zu simulieren und benutzerdefinierte Ein- und Ausgaben beim Herstellen einer Verbindung zu berücksichtigen. In der Spezifikation erfahren Sie, was native Knoten alles können.

Nachdem wir nun unser grundlegendes Muster zum Erstellen benutzerdefinierter Effekte kennen, bestand der nächste Schritt darin, dem benutzerdefinierten Knoten ein benutzerdefiniertes Verhalten zu geben. Sehen wir uns einen Slapback-Verzögerungsknoten an.

Slapback wie du meinst es

Das Slapback-Echo, manchmal auch als Slapback-Echo bezeichnet, ist ein klassischer Effekt, der bei verschiedenen Instrumenten verwendet wird – vom Gesang im 50er-Jahre-Stil bis hin zu Surf-Gitarren. Bei diesem Effekt wird der Klang von eingehenden Geräuschen mit einer leichten Verzögerung von etwa 75–250 Millisekunden als Kopie abgespielt. Dies vermittelt das Gefühl, dass der Ton zurückgeklappt wird, daher auch der Name. Den Effekt können wir so 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 Ihnen vielleicht bereits bemerkt haben, könnte diese Verzögerung auch mit größeren Verzögerungszeiten verwendet werden und damit zu einer regelmäßigen Mono-Verzögerung mit Feedback werden. Hier ist ein Beispiel, in dem diese Verzögerung verwendet wird.

Audio weiterleiten

Wenn du in professionellen Audioanwendungen mit verschiedenen Instrumenten und Musikstücken arbeitest, ist ein flexibles Routingsystem wichtig, mit dem du Töne effektiv mischen und modulieren kannst. Für JAM mit Chrome haben wir ein Audiobussystem entwickelt, das dem System für physische Mixingboards ähnelt. So können wir alle Instrumente, die einen Halleffekt benötigen, an einen gemeinsamen Bus oder Kanal anschließen und diesen dann diesem Bus hinzufügen, anstatt jedem einzelnen Instrument einen Nachhall hinzuzufügen. Dies ist eine bedeutende Optimierung und wird dringend empfohlen, etwas Ähnliches durchzuführen, sobald Sie anfangen, komplexere Anwendungen zu erstellen.

Weiterleitung des AudioBus

Glücklicherweise ist das in Web Audio wirklich einfach. Wir können im Grunde das für die Effekte definierte Raster verwenden und es 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);
    };
};

Diese würde wie folgt verwendet werden:

//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 voilà: Wir haben Verzögerung, Entzerrung und Nachhall (was ein ziemlich teurer Effekt und in Sachen Leistung ist) auf die Hälfte der Kosten angewendet, als ob wir die Effekte auf jedes einzelne Instrument anwenden würden. Wenn wir den Bus etwas würziger machen wollten, könnten wir zwei neue Verstärkungsknoten hinzufügen – preGain und postGain –, mit denen wir die Töne in einem Bus auf zwei verschiedene Arten ab- oder ausblenden können. PreGain wird vor den Effekten und postGain an das Ende der Kette gesetzt. Wenn wir die preGain dann ausblenden, hören die Effekte auch nach dem Erreichen des unteren Pegels eine große Resonanz. Beim Ausblenden von postGain wird der gesamte Ton gleichzeitig stummgeschaltet.

Wie geht es von hier aus?

Die hier beschriebenen Methoden können und sollten weiterentwickelt werden. Dinge wie die Eingabe und Ausgabe der benutzerdefinierten Knoten sowie die Verbindungsmethoden können/sollten durch die Vererbung auf Prototypen-Basis implementiert werden. Busse sollten Effekte dynamisch erzeugen können, indem sie eine Liste von Effekten übergeben.

Zur Feier der Veröffentlichung von JAM mit Chrome haben wir beschlossen, unser Framework für Effekte als Open Source zur Verfügung zu stellen. Wenn Ihnen diese kurze Einführung gefallen hat, sehen Sie sich doch mal um und tragen Sie dazu bei. Hier finden Sie eine Diskussion zur Standardisierung eines Formats für benutzerdefinierte Web-Audio-Entitäten. Mach mit!