RSS Twitter Facebook


« 2019年01月 | 2019年02月のアーカイブ |

2019/02/13

webaudio-macronodes


これは GitHub に置いてあるリポジトリの説明です。

リポジトリ:webaudio-macronodes

Web Audio API のノードは全体的に機能の粒度が細かめで組み合わせの自由度は高いですが、反面何らかのエフェクターのようなものを構成するには複数のノードを組み合わせる必要があり、エフェクターの内部構造に関する知識を必要とします。

前回の記事で「[Web Audio API] 既存ノードを組み合わせたカスタムノードの作り方」というのを書いたのですが、これを使っていわゆるギターエフェクター的な機能を単一のノードにまとめる事ができます。

webaudio-macronodes は良くあるエフェクターのセットを Web Audio API のノードとして使えるようにまとめたものです。特長は、ノードの生成や接続、パラメータへのアクセスに関して、例えば GainNode や BiquadFilterNode のような通常の Web Audio API のノードとまったく同じ使い方ができる事です。


使い方

本体は webaudio-macronodes.js というファイルになるのでこれを読み込みます。GitHub pages から直接読み込むならば、次の行を HTML に追加します。
<script src="https://g200kg.github.io/webaudio-macronodes/webaudio-macronodes.js"></script>
これによって webaudio-macronodes が持っている 10 個程度のエフェクトが使えるようになります。


ノードの生成

コンストラクタを使って生成します。audioContext.createGain() のようなファクトリメソッドはサポートしていません。 例えば、

toneControl = new Macro_ToneControlNode(audioContext);
また、作成時にパラメータの初期値を渡す事もできます:

toneControl = new Macro_ToneControlNode(audioContext, {bass:10, mid:0 treble:-3});


ノードの接続

通常のノードと同様に connect() メソッドで接続して行きます。このノードに対する接続が Web Audio API の通常のノードと同様に connect() メソッドを使う事でできるのが webaudio-macronodes の肝の部分です。

例えば

sourceNode.connect(toneControl).connect(audioContext.destination);


パラメータへのアクセス

webaudio-macronodes の各ノードはそれぞれエフェクトのかかり方を調整するパラメータを持っています。これらのパラメータは基本的に AudioParam 型になっていますので、setValueAtTime() 等のオートメーション関数を使うなり、他のノードの信号を connect() するなり自由にアクセスできます。

例えば

now = audioContext.currentTime;
toneControl.bass.setValueAtTime(0,now);
toneControl.bass.linearRampToValueAtTime(12,now+10);


エフェクトの一覧

パラメータが取る値の範囲等の詳細はリポジトリを見てください。

リポジトリ : webaudio-macronodes
エフェクトパラメータ説明
Macro_ToneControlNodetreble
mid
bass
高音/中音/低音のトーンコントロール
Macro_GraphicEqNodeeq[]7 バンド・グラフィックイコライザー
Macro_ChorusNoderate
depth
一般的なコーラスエフェクト
Macro_PhaserNoderate
depth
一般的なフェイザーエフェクト
Macro_DeepPhaserNoderate
depth
resonance
フィードバック付きのフェイザーエフェクト
Macro_DelayNodedelayTime
feedback
mix
一般的なディレイエフェクト
Macro_PingPongDelayNodedelayTime
feedback
mix
左右チャンネルから交互に出るディレイエフェクト
Macro_BitCrusherNodebitsビット深度を減少させるビットクラッシャーエフェクト
Macro_OverDriveNodedrive
level
サチュレーションによる歪みを付加するオーバードライブエフェクト
Macro_FuzzNodefuzz
level
倍音の強い歪みを付加するファズエフェクト
Macro_AutoWahNodefrequency
Q
sense
音の大きさでフィルターを変化させるオートワウエフェクト

posted by g200kg : 8:50 AM : PermaLink

2019/02/01

[Web Audio API] 既存ノードを組み合わせたカスタムノードの作り方



少し前に GitHub にサンプルのリポジトリだけ置いてあったのですが、これは Web Audio API で幾つかのノードを組み合わせて独自のノードを作る方法の説明です。処理をすべてスクラッチから作るなら AudioWorkletNode を使えば良いのですが、単に既存のノードを組み合わせて1つのノードとして動作させるには少し裏技的な手法が必要になります。

GitHub : g200kg/webaudio-customnode

基本的に Web Audio API の各種ノードの機能はかなり細かく分けられていますので、一般的なエフェクターのようにまとまった機能のものを作るには幾つかのノードを組み合わせる必要があります。典型的な例がディレイエフェクトで、Web Audio API の DelayNode は信号を指定の時間遅らせるだけのものですから、エフェクター的ないわゆるディレイ効果を作るには、元音とのミックスや音を繰り返させるフィードバックの機能が欲しい所です。

上の図が今回作るサンプルのカスタムノード、MyDelayNode の構造です。入力端及び出力端は必ずしも GainNode でなくても良いのですが、単一の connect() メソッドでの接続ができるように信号が 1 本にまとまっている必要があります。

この MyDelayNode を標準の1つのノードと同等の方法で使えるようにします。つまり、
  1. var myNode = new MyNode(audiocontext, option) // コンストラクタでノードを作成できる。
  2. myNode.parameter.setValueAtTime(0,0) // 必要なパラメータにアクセスしオートメーションも使える。
  3. myNode.connect(otherNode) // ノードから出力を接続できる。
  4. otherNode.connect(myNode) // このノードに対して接続できる。
これを class にしたものが下のコードです。


class MyDelayNode extends GainNode {
  constructor(actx,opt){
    super(actx);
    // Internal Nodes
    this._delay = new DelayNode(actx, {delayTime:0.5});
    this._mix = new GainNode(actx, {gain:0.5});
    this._feedback = new GainNode(actx, {gain:0.5});
    this._output = new GainNode(actx, {gain:1.0});
    // Export parameters
    this.delayTime = this._delay.delayTime;
    this.feedback = this._feedback.gain;
    this.mix = this._mix.gain;
    // Options setup
    for(let k in opt){
      switch(k){
      case 'delayTime': this.delayTime.value = opt[k];
        break;
      case 'feedback': this.feedback.value = opt[k];
        break;
      case 'mix': this.mix.value = opt[k];
        break;
      }
    }
    this._inputConnect = this.connect;   // input side, connect of super class
    this.connect = this._outputConnect;  // connect() method of output
    this._inputConnect(this._delay).connect(this._mix).connect(this._output);
    this._inputConnect(this._output);
    this._delay.connect(this._feedback).connect(this._delay);
  }
  _outputConnect(to,output,input){
    return this._output.connect(to,output,input);
  }
}
ポイントはこのカスタムノードに対する接続の処理の仕方です。他のノードから connect() メソッドで接続するためには、このノード自体が AudioNode である必要があるので、既存ノードの子クラスとして作成します。
  • 1 行目、クラスは既存のノード、例えば GainNode の子クラスとして extends で派生させます。適当に作ったクラスでは他のノードからの接続ができません。この親クラスのノードが入力端のノードとなります。
  • 24、25 行目、このカスタムノードからの出力となる connect() メソッドは子クラス側でオーバーライドすれば良いのですが、その前に元の connect() メソッドを確保しておきます。ここで確保した inputConnect で、入力端子である親クラスの GainNode に入ってくる信号を取り出す事ができます。
  • 4 ~ 8 行目、内部で使用するノードです。
  • 9 ~ 12 行目、カスタムノードとして必要なパラメータを外に引っ張り出します。
MyDelayNode サンプル


もう1つの例、ピンポンディレイです。これはディレイ音が左右のチャンネルから交互に出るエフェクトで、DelayNode を分割して途中からタップを出し、左右のチャンネルに振り分けています。

入出力の接続に関しては前のサンプルと同様ですが、ここで問題になるのは、2 つある DelayNode の delayTime パラメータをどうやって制御するかという点です。単一の AudioParam 型パラメータから 2 つの DeleyNode を連動して動かす必要があります。これには ConstantSourceNode が使えます。ConstantSourceNode の offset パラメータを外部に対して delayTime パラメータとして露出させ、内部では connect() メソッドによるノード間の接続で DelayNode の delayTime に接続します。


class MyPingPongDelayNode extends GainNode {
  constructor(actx,opt){
    super(actx);
    // Internal Nodes
    this._delay1 = new DelayNode(actx, {delayTime:0.5});
    this._delay2 = new DelayNode(actx, {delayTime:0.5});
    this._merger = new ChannelMergerNode(actx);
    this._constant = new ConstantSourceNode(actx, {offset:0.5});
    this._mix = new GainNode(actx, {gain:0.5});
    this._feedback = new GainNode(actx, {gain:0.5});
    this._output = new GainNode(actx, {gain:1.0});
    // Export parameters
    this.delayTime = this._constant.offset;
    this.feedback = this._feedback.gain;
    this.mix = this._mix.gain;
    // Options setup
    for(let k in opt){
      switch(k){
      case 'delayTime': this.delayTime.value = opt[k];
        break;
      case 'feedback': this.feedback.value = opt[k];
        break;
      case 'mix': this.mix.value = opt[k];
        break;
      }
    }
    this._inputConnect=this.connect;   // input side, connect of super class
    this.connect=this._outputConnect;  // connect() method of output
    this._inputConnect(this._delay1).connect(this._delay2).connect(this._merger,0,0).connect(this._mix).connect(this._output);
    this._delay1.connect(this._merger,0,1);
    this._inputConnect(this._output);
    this._delay2.connect(this._feedback).connect(this._delay1);
    this._constant.connect(this._delay1.delayTime);
    this._constant.connect(this._delay2.delayTime);
    this._constant.start();
  }
  _outputConnect(to, output, input){
    return this._output.connect(to, output, input);
  }
}

MyPingPongDelayNode サンプル


このような手法で class の登録さえすれば、標準のノードと同様の使い方が可能な、独自のノードが使えるようになります。あまり複雑な構造になりすぎるとパフォーマンスに影響が出てきそうではありますが、内部のノードの JavaScript の処理をノードの接続で置き換えるのがポイントになります。

なお、Web Audio API の V2 では、複数のノードを組み合わせたものを格納できるコンテナとなるノードの検討がされるようです。これが使えるようになれば、もっと自然な方法でこういう事ができるようになると思いますが、現在の仕様内でもやり方を工夫すれば同様の事ができますので、覚えておけば役に立つ事もあるかも知れません。

posted by g200kg : 4:13 AM : PermaLink

« 2019年01月 | 2019年02月のアーカイブ |


g200kg