改めて、ウエーブテーブルシンセサイザを作る(1)


久しぶりにlogue SDKでのプログラミングの話題です。
今回はウエーブテーブルシンセシスを扱います。

ウエーブテーブル方式のシンセシスは、1波長分のサンプリングデータを納めた「ウエーブテーブル」を繰り返し読み出すことで音を生成する方式です。1つのテーブルでも、読み出す速度を変えることで異なる音高の音を生成できます。

ただし、音高を上げると波形に含まれている倍音成分の周波数も高くなり、その周波数がサンプリング周波数の1/2を超えるとエイリアスノイズが発生します。そのため、あらかじめ音高に合わせて複数のウエーブテーブルを用意し、そのウエーブテーブルの倍音成分は使用する音高に合わせて調節しておくことが一般的です。

logue SDKでは、あらかじめサイン波、矩形波、鋸波、パラボラ波のウエーブテーブルが用意されています。サイン波には倍音成分は無いので、ウエーブテーブルは1つだけです。それ以外の波形は、音域に合わせて7つのウエーブテーブルが用意されています。

今回の記事の目的は、SDKで用意されたウエーブテーブルではなく、独自のウエーブテーブルを使ったオシレータを実装することです。

最初は、サイン波から始めます。サイン波は「純音」と言われるくらいで、倍音成分はありません。ですからウエーブテーブルは1つで済みます。一方で、実装にミスがあれば純音以外の音が出てくるのですぐに気づけるというメリットがあります。

まずはlogue SDKのサイン波生成の実装を見てみます。osc_api.hの135行目からです。float osc_sinf(float x)の実装部分を抜き出しました。

ウエーブテーブルのサイズはk_wt_sine_size、これは2^7なので128サンプルとなります。
実際には、このテーブルに入っているデータは\(0\ldots\pi\)までの範囲です。サイン波の形状は対称性があるので、\(\pi\ldots2\pi\)の範囲のデータは\(0\ldots\pi\)のデータの符合を反転することで生成します。従って1波長あたりでは256サンプルとなります。

osc_sinfのパラメータxは位相を示す値です。位相が0なら波形の先頭、1.0なら末尾です。x自体は1以上でもOKですが、同じ波形を繰り返すので、例えばx=0.1、x=1.1、x=2.1、…はどれも同じ結果が返ります。つまり整数部は無視してよいので、変数pxの小数部分を取り出しています。

pにテーブルサイズ(256)を掛けると、テーブルから読み出すべき値のインデックスが分かります。実際には前述のようにテーブルは波形の前半部分しか用意していませんので、インデックスが127の次は0に戻ります。この計算には128で割った余りを計算する代わりに、0x7fとANDを取っています。

サンプルとサンプルの間の値は線形補間で生成しています。logue SDKでは線形補間の関数linintf(t, a, b)が提供されています。t=0なら結果はa、t=1なら結果はbとして、aからbまでを直線でで補間した値が得られます。

補間のためには、上の図でx0, x1という2つの値が常に必要となります。x0の値域は0..127ですが、x1の値域は1..128となります。そのため、ウエーブテーブル自体は128ですが、末尾に129番目の値が用意されています。これに対応して、ウエーブテーブルwt_sine_lut_fのサイズk_wt_sine_lut_size(k_wt_sine_size+1)と定義されています。

この末尾の値は、テーブルの先頭の値と同じ値のはずです。ただ・・・上記のコードを見ると、x1はk_wt_sine_mask(=127)でマスクされていますので、実際にはこの末尾の値はアクセスされず、代わりにテーブルの先頭の値がアクセスされるように思います。

長くなってきたので、次回に続きます。

コメント