事例紹介 - Chrome での JAM

音声をロックに仕上げる方法

はじめに

JAM with Chrome は、Google が作成したウェブベースの音楽プロジェクトです。JAM with Chrome では、世界中のユーザーがバンドを結成し、ブラウザ内でリアルタイムでジャムセッションを行うことができます。DinahMoe は、このプロジェクトに参加できたことを大変光栄に思います。私たちの役割は、アプリの音楽を制作し、音楽コンポーネントを設計して開発することでした。開発は主に 3 つの領域で行われました。MIDI 再生、ソフトウェア サンプラー、オーディオ エフェクト、ルーティング、ミキシングを含む「音楽ワークステーション」、音楽をリアルタイムでインタラクティブに制御する音楽ロジック エンジン、セッション内のすべてのプレーヤーが音楽を正確に同時に聞くようにする同期コンポーネントです。これは、一緒に演奏するための前提条件です。

可能な限り高いレベルの信頼性、精度、音質を実現するため、Web Audio API を使用しました。このケーススタディでは、直面した課題とその解決方法について説明します。HTML5Rocks には、Web Audio の入門記事がすでにいくつかあります。ここでは、すぐに本題に入ります。

カスタム オーディオ エフェクトの作成

Web Audio API の仕様には多くの便利なエフェクトが含まれていますが、Chrome 版 JAM の楽器には、より複雑なエフェクトが必要でした。たとえば、Web Audio にはネイティブの遅延ノードがありますが、ステレオ遅延、ピンポン遅延、スラブバック遅延など、遅延には多くの種類があります。幸い、これらの効果はすべて、ネイティブ エフェクト ノードと想像力を使って Web Audio で作成できます。

ネイティブ ノードと独自のカスタム エフェクトを可能な限り透過的に使用できるようにするため、これを実現できるラッパー形式を作成する必要があると判断しました。Web Audio のネイティブ ノードは、connect メソッドを使用してノードをリンクするため、この動作をエミュレートする必要がありました。基本的な考え方は次のとおりです。

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

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

このパターンでは、ネイティブ ノードに非常に近づきます。使用例を見てみましょう。

//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);
カスタムノードのルーティング

カスタムノードとネイティブ ノードの唯一の違いは、カスタムノードの入力プロパティに接続する必要があることです。回避する方法は他にもあると思いますが、目的を達成するにはこれで十分でした。このパターンは、ネイティブ AudioNode の切断方法をシミュレートしたり、接続時にユーザー定義の入力/出力に対応したりするように、さらに開発できます。ネイティブ ノードの機能については、仕様をご覧ください。

カスタム エフェクトを作成する基本的なパターンが決まったので、次はカスタム ノードにカスタム動作を実際に設定します。スラッバック ディレイ ノードを見てみましょう。

本格的なスナップバック

スナップバック ディレイ(スナップバック エコーとも呼ばれます)は、50 年代スタイルのボーカルからサーフギターまで、さまざまな楽器で使用されるクラシックなエフェクトです。このエフェクトは、受信した音声をコピーし、約 75 ~ 250 ミリ秒のわずかな遅延を付けて再生します。音が跳ね返ってくるような感覚が得られるため、この名前が付けられています。この効果は次のように作成できます。

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);
    };
};
スラブバック ノードの内部ルーティング

ご存じの方も多いと思いますが、このディレイは長いディレイ時間でも使用でき、フィードバック付きの通常のモノラル ディレイになります。以下に、この遅延を使用した例を示します。

オーディオのルーティング

プロ用オーディオ アプリケーションでさまざまな楽器や楽曲パートを扱う場合は、音を効果的にミックスして変調できる柔軟なルーティング システムが必要です。JAM with Chrome では、物理的なミキシング ボードに見られるようなオーディオ バス システムを開発しました。これにより、リバーブ エフェクトが必要なすべての楽器を共通のバス(チャンネル)に接続し、個々の楽器にリバーブを追加するのではなく、そのバスにリバーブを追加できます。これは大きな最適化であり、より複雑なアプリケーションを扱う場合は、すぐに同様のことを行うこと強くおすすめします。

AudioBus のルーティング

幸い、これは Web Audio で簡単に実現できます。基本的には、エフェクト用に定義したスケルトンをそのまま使用できます。

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

次のように使用します。

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

これで、各楽器にエフェクトを適用する場合の半分のコストで、ディレイ、イコライゼーション、リバーブ(パフォーマンス的には比較的高価なエフェクト)を適用できました。バスにさらにスパイスを加えたい場合は、2 つの新しいゲインノード(preGain と postGain)を追加します。これにより、バス内の音を 2 つの異なる方法でオフにしたり、フェードしたりできます。preGain はエフェクトの前に配置し、postGain はチェーンの最後に配置します。preGain をフェードすると、ゲインが最小に達した後もエフェクトは共鳴しますが、postGain をフェードすると、すべての音声が同時にミュートされます。

次のステップ

ここで説明した方法は、さらに開発できます。また、開発すべきです。カスタムノードの入出力や接続メソッドなどは、プロトタイプベースの継承を使用して実装できます。バスは、エフェクトのリストを渡すことで、エフェクトを動的に作成できる必要があります。

Chrome で JAM をリリースすることを記念して、エフェクトのフレームワークをオープンソースにすることにしました。この簡単な紹介に興味をお持ちいただけましたら、ぜひご覧ください。また、ご協力いただければ幸いです。カスタム Web Audio エンティティの形式の標準化に関する議論は、こちらで進行中です。参加しましょう