Processingでサウンドエフェクト処理をはじめよう

第5回 トーンコントロール [その3]

③ BassToneタブのソース

今回は、BassToneタブのソースの説明をします。

class BassTone implements AudioEffect

クラスの宣言のところで、AudioEffectインターフェイスを使って実装すると示されています。
AudioEffectインターフェイスには、以下の2つのメソッドが定義されています。

void process(float[] samp)
void process(float[] left, float[] right)

インターフェイスにはメソッドの定義がされていますが、そのメソッドの中身は空です。このインターフェイスを使うクラスの中で、このメソッドの中身の処理を実装することになります。 こうすることで、AudioEffect を使った BassTone クラスのオブジェクト bassには、process() メソッドがあることが分かるので、 setup() の中の

player.addEffect(down);
player.addEffect(bass);
player.addEffect(hi);

のように player にエフェクト処理を追加することができます。 player は、追加されたオブジェクトの process()メソッドを呼び出すことで、エフェクト処理を実行します。 bassオブジェクトは、process()メソッドのインタフェースを持っているわけですね。 なので、逆に、AudioEffect を継承していないクラスのオブジェクトは、addEffect()メソッドで追加することはできません。
process()メソッドは、以下の様になっています。

void process(float[] samp)
{
  biquad_process(samp, lx, ly);
}

void process(float[] left, float[] right)
{
  biquad_process(left, lx, ly);
  biquad_process(right, rx, ry);
}

クラス内の biquad_process() を呼び出していますね。 biquad_process() の中で、低音を調整する処理をしています。
ところで、process()メソッドは2種類あります。 引数が samp の一つだけの場合は、サウンドデータがモノラルの場合に呼び出されます。引数が、left, right の2つの場合は、ステレオの場合に呼び出されます。 ステレオの場合は、baiquad_process() を2回呼び出すことで、それぞれLチャンネル、Rチャンネルの処理をしています。
samp, left, right の中には、

player = minim.loadFile("sample.wav", 1024);

で指定した1020個の分のデータが入っています。 この process()メソッドは、1020個のデータが入れ替わったときに呼び出されます。 サウンドデータは、1024個よりはかなり多いので、1024個分再生が終わると、次の1024個分読み込むという処理が、データの最後になるまで繰り返されます。
biquad_process()lx, ly, rx, ry の引数は何でしょう? トーンコントロールに使われるデジタルフィルタ処理には、現時点データより1つ前以上の時点のデータが必要になります。 sampデータは1024個がひとまとまりで入っているのですが、もし、1番目のデータより1つ前のデータが欲しい場合は、 1024個の中には含まれていません。そこで、一つ前の1024個のデータの最後のデータを保存しておけば、それが1つ前の時点のデータになります。
biquad_process() では、1つ前と2つ前が必要なので、

float[] lx = new float[2];
float[] ly = new float[2];
float[] rx = new float[2];
float[] ry = new float[2];

で、保存用の配列を確保しておき、どの保存データを使うのかを、lx, ly, rx, ry で指定しています。

トーンのエフェクト処理は、biquad_process() の中で実装されています。 「Audio-EQ-cookbook」の中で、ローシェルフやハイシェルフに使われるバイクアッドフィルタは、以下の式で計算できると書かれています。

y[n] = (b0/a0)*x[n] + (b1/a0)*x[n-1] + (b2/a0)*x[n-2]
                    - (a1/a0)*y[n-1] - (a2/a0)*y[n-2]

biquad_process() の中で、この式をプログラミングしています。
out[0], out[1] は、samp に入っているより前の時点のデータをx, yから得る必要があるので、分けて書かれています。 それ以降は、samp の中でまかなえるので、for文で繰り返しています。
フィルタ処理が終わったら

x[0] = samp[samp.length - 2];
x[1] = samp[samp.length - 1];
y[0] = out[out.length - 2];
y[1] = out[out.length - 1];

samp の最後と最後から一つ前のデータを x, y に保存しています。
最後に、

arraycopy(out, samp);

で、out の中身を sam pにコピーしています。 player は、この samp のデータを使って再生をするので、トーンコントロールが聞いたデータを再生することになります。

クラス名 BassTone と同じ名前の BassTone() と言うメソッドがありますね。 これは、コンストラクタと言うもので、

bass = new BassTone(FS, LF, LGAIN);

とオブジェクトを生成するときに呼び出されるものです。 この中で、lx, ly, rx, ry0(無音)で初期化し、float fs, float f, float g に渡された、FS, LF, LGAIN の値で、 biquad_process() の中で必要となる、係数

float b0, b1, b2, a0, a1, a2;

を計算しています。
「Audio-EQ-cookbook」内の計算式は省きます。

④ HiToneタブのソース

HiToneタブのソースも、係数の計算式が違うだけで、処理はBassToneと同様になります。

⑤ DoGainタブのソース

最後にDoGainタブのソースです。
BassTone, HiTone と同じく、AudioEffect インターフェイスを使ってますね。 と言うことは、process()メソッドの実装があるはずです。
今回は音量の調整なので、DownGainコンストラクタで geinメンバ変数に音量を渡し、 process()メソッドの中で、各データに gain を掛けて音量を調整しています。 簡単ですね。

これで、トーンコントロールの一通りの説明が終わりました。
少し長い説明になり、お疲れ様でした。

次回は、エコー処理を扱います。

「Processingでサウンドエフェクト処理をはじめよう」一覧に戻る