ボコーダーを作ってみる(4)


前回、前々回と既存のボコーダーを調べてきましたが、今回はこれらの知識もふまえて、ほぼ最低限の機能をNTS-1 mkII用にlogue SDK v2で実装した、シンプルなボコーダーを紹介します。

仕様

既存のボコーダーのスペックをざっくりまとめると

・フィルターバンクのBPFの個数は10~20
・1つのフィルタは24db/oct(アクティブフィルタ×2)
・初段と2段目でフィルタの周波数を少しだけずらす
・フィルタのQは6~7くらい
・周波数帯域は、200Hz~5KHzくらいをカバー
・エンベロープフォロワはもちろん必須
・オシレータの基本は鋸波

といった感じでした。

フィルターバンクのBPFはキャリア側とモジュレータ側に必要になり、それぞれ2つカスケードにしますので、10バンドなら40個のBPFが必要になります。

今回は、フィルターバンクは8バンドにしてみます。これでも32個のフィルタを同時に計算する必要があります。

一般論としてはフィルタの個数が多ければ、それだけ周波数の解像度が上がるので、よりモジュレータの特徴を捉えやすくなります。今回実験してみたところでは、8バンドでもそこそこ人声の特徴は抽出できました。

オシレータはとりあえず鋸波を用意することにします。

フィルタのパラメータ

バンドパスフィルタの個々の周波数は、今回は以下のように計算で求めてみます。

まず、フィルタの「中心周波数\(f_c\)と通過帯域の幅\(f_b\)の比」を決めます。そして、これを決めると、フォルマント周波数の帯域である100Hz~5KHzくらいの範囲を、何個のBPFでカバーするかが決まります。

いきなり抽象的なパラメータが出てきましたが、実際は、BPFの通過帯域の広さを決めるということです。ただしその幅は絶対値ではなく、中心周波数との比率で決めます。

例えば、5オクターブの鍵盤の1オクターブ分をカバーするBPFを5個作ることを考えて下さい。音階の周波数は等比級数的なので、通過帯域は\(fから2f\)、\(2fから4f\)、・・・というようになりますね。このとき、中心周波数は\(1.5f\)、\(3f\)、・・・となり、通過帯域幅は\(f\)、\(2f\)、・・・というように、どちらも等比級数になります。そして、中心周波数と通過帯域幅の比は常に1.5となっています。

今回は、この「中心周波数\(f_c\)と通過帯域の幅\(f_b\)の比」を2.45(\(\sqrt{6}\))としました。

すると、フィルターバンクの最低帯域のBPFの通過帯域の下限の周波数\(f_L\)が与えられたとき、各BPFの\(f_c\)と\(f_b\)は、以下のようにして計算できます。

最低帯域のBPFの通過帯域は、\(f_L\)から\(f_L + f_b\)までで、この時このフィルタの中心周波数\(f_c = \sqrt{f_L (f_L + f_b)}\)となります。ここで\(Q = f_c / f_b\)と置きますと、\(f_c = Qf_b\)ですので、
$$ \begin{align}
Qf_b &= \sqrt{f_L (f_L + f_b)} \\
Q^2f_b^2 &= f_L (f_L + f_b) \\
Q^2f_b^2 – f_Lf_b – f_L^2 &= 0
\end{align} $$
ここで2次方程式の解の公式を使うと、
$$ \begin{align}
f_b &= \frac{f_L \pm \sqrt{f_L^2 + 4Q^2f_L^2}}{2Q^2} \\
& = \frac{1 \pm \sqrt{1 + 4Q^2}}{2Q^2}f_L \\
\end{align} $$
となりますから、このフィルタの通過帯域の上限側が\(f_L + f_b = (1 + \frac{1 \pm \sqrt{1 + 4Q^2}}{2Q^2})f_L\)と求まります。(2つの解のうち正のほうを取ります。\(Q=\sqrt{6}\)なら\(f_b = \frac{1}{2}f_L\)となります。)

2番目のフィルタの通過帯域は、\(f_L\)を上のフィルタの上限側と置いて求めることができます。
このようにして、\(f_L = 200Hz\)、\(Q=\sqrt{6}\)として表計算ソフトで求めた8個分のフィルタの帯域が以下です。200Hzから5.1KHzまでをカバーしています。

  \(f_L\) \(f_b\) \(f_H\) \(f_c\)
1 200 100 300 245
2 300 150 450 367
3 450 225 675 551
4 675 338 1013 827
5 1013 506 1519 1240
6 1519 759 2278 1860
7 2278 1139 3417 2790
8 3417 1709 5126 4185

実装

logue SDKでボコーダーを実装するには、バンドパスフィルタが必要になりますが、今回フィルタの実装はlogue SDK付属のBiQuadフィルタのライブラリを使います。BiQuadフィルタはFirst OrderとSecond Orderがあり、それぞれ減衰は6db/Octと12db/Octです。今回は2段で24db/Octとするため、Second Orderを用います。

パラメータとしては中心周波数とQ値を与える必要があります。Q値は\(f_c/f_b\)ですが、上の計算に用いた2.5ではなく、2倍のQ=6としておきます。Q=2.5では、BPFの通過帯域がその隣のBPFとぴったりくっついてしまいますが、BPFは24dB/octでしか減衰しないので、周波数特性の裾野が重なりすぎてしまいます。

下図にQ=6の場合の8つのバンドパスフィルタの特性、およびそれらを合算したフィルターバンク全体の特性(点線)を示します。

次にエンベロープフォロワですが、今回の実装は以下の簡単なものです。

(1)単純に減衰していく関数\( f(t + \Delta t) = k f(t)\)でエンベロープフォロワの値を求めます。
(2)エンベロープフォロワへの入力値が\(kf(t)\)より大きければ、その値を\(k f(t)\)の代わりに\(f(t + \Delta t)\)の値として用います。

これだけですが、\(k\)を適切に(0.995以上1.0未満)選べば、そこそこ動作します。

この\(k\)の値が1に近づくほど、エンベロープは減衰しにくくなり、リリースタイムが長くなります。デジタルでの実装の場合は\(\Delta t\)はサンプリング周期なので、例えば\(k = 0.995\) のとき \(f(t)\) が1から0.5まで減衰する時間は
$$ln(0.5) / ln(0.995) = -0.693 / -0.005 = 138.3 samples = 約2.9 msec $$
と計算できます。

最後にオシレータですが、キャリアの信号は倍音成分を多く含んでほしいのですが、以前調べたところでは、logue SDK付属のosc_sawf()関数で得られる鋸波はやや倍音成分が少ないことがあります。そのため、今回はAPIを使わず、ナイーブな波形実装(\(y=2 \phi – 1\))を使います。

実装したコード

コード全体は以下のリポジトリに置いてあります。バイナリはこちらです。ノブAでエンベロープフォロワのパラメータ(上の説明における\(k\))、ノブBでモジュレータ(Audio Inからの音声)の音量調整ができます。

GitHub - boochow/simple_vocoder: An example implementation of 8-band vocoder using logue SDK
An example implementation of 8-band vocoder using logue SDK - boochow/simple_vocoder

以下は、ボコーダー部分のコードです。それほど長くはありません。

Init()(19行目)の中でフィルタや関連パラメータを初期化しています。実行時は、1サンプルごとにProcess()にキャリアとモジュレータのサンプルを渡せば、ボコーダの出力が得られます。

30行目のゲインの計算ですが、高域のBPFほど大きなゲインとなるように計算しています。キャリアが鋸波、モジュレータがサイン波のとき、フィルターバンクがカバーしている範囲であればどんな周波数でも、ボコーダの出力が概ね同じくらいのピーク値になるようにしています。

次の音声ファイルはこのボコーダーの音声を入力した場合、およびサイン波のスイープ(50Hz~10KHz)を入力した場合の出力です。BPFに応じて、音が強くなるタイミングが8回あるのが分かると思います。なお、モジュレータに使っている音声はmoogのボコーダーのデモから拝借しました。

改良点など

今回はボコーダーの最も基本的な部分だけを、なるべく短いコードでお見せしましたが、より品質を高めるためには

・オシレータをパラフォニックにする
・BPFの数(フィルターバンクのチャンネル数)を増やす
・BPFでカバーしていない高域成分の処理の追加(高域だけHPFで取り出して出力にミックスするなど)
・キャリア用のBPFの帯域をモジュレータのBPFの帯域からずらしてフォルマントの分離を良くする(VP330のように)

などの方策があります。これらの機能を追加した正式版?は後日公開予定です。

コメント