FPGA 版 FM 音源 (43) -- YMF297 (OPN3/OPL3) 測定 (8) -- ビブラート

OPN / OPL 等の FM 音源チップにはハードウェアによるビブラート機能が搭載されています。
現在の FM 音源プログラムでは、「音源チップ」に相当する部分には「ハードウェア・ビブラート」に相当するものは実装せず、「音源ドライバ」に相当する部分でソフト的に F-Number を書き換えています。
これはビブラートに限らず、ポルタメントやピッチ・ベンド等のピッチ関連の処理と合わせて行なっています。
今回は、YMF297 を OPL3 モードに設定してビブラートの様子を測定してみました。
(2016 年 9 月 26 日追記: トレモロLFO との区別を明確にするために、単に「LFO」としていた表記を「VIB_LFO」と改めました。)
音源パラメタを

WS = 0 (サイン波)
FNUM = 0x100 (b8 のみ 1)
BLOCK = 7 (最上位オクターブ)
MULT = 4 (4 倍)
VIB = 1 (ビブラート ON)
DVB = 1 (ビブラート「強」、14 セント)

という設定で 48 kHz の SPDIF 出力を経由して波形をチャプチャし、波形編集ソフトのスペクトラム表示で見たものを下に示します。

この設定では (中心) 周波数は 6 kHz となります。 上の図で明るい黄色になっている部分がピーク周波数の位置です。
ピークは周波数方向に広がっており正確な周波数が読み取れないので、オレンジ色の背景の中に紫色の長方形として見えている「ディップ」部分に注目します。
まず、時間軸方向には 8 個を周期として繰り返していることが分かります。
上の図では表示の一部分だけを示しており、時間軸の目盛り部分が表示されていませんが、実際に 1 周期の長さを読み取ると約 0.17 秒となり、VIB_LFO 周波数としては約 5.9 Hz となります。
OPL4 (YMF278B) のアプリケーション・マニュアルにはビブラート周波数は 6.0 Hz と記載されているので、ほぼ一致します。
さらに、

48 [kHz] / 5.9 [Hz] = 8135.6 ≈ 8192 = 8 × 1024

となるので、サンプリング周波数 (48 kHz) を 1024 分周したもの (fs/1024) をクロックとする 8 進カウンタで VIB_LFO を構成できることが分かります。
VIB_LFO 出力は振幅方向には 5 値で構成され、最も簡単には

-2, -1, 0, +1, +2

と表現できます。
上の図でピーク周波数の上側に位置するディップから、VIB_LFO の各値に対応する周波数を読み取ると、

f(+2) = 6132 Hz
f(+1) = 6108 Hz
f(0) = 6086 Hz
f(-1) = 6062 Hz
f(-2) = 6038 Hz

となります。 これから中心周波数 f(0) に対する比率を求めると、

f(+2) / f(0) = 1.0076 = 257.9/256 ≈ 1 + 2/256
f(+1) / f(0) = 1.0036 = 256.9/256 ≈ 1 + 1/256
f(-1) / f(0) = 0.9961 = 255.0/256 ≈ 1 - 1/256
f(-2) / f(0) = 0.9921 = 254.0/256 ≈ 1 - 2/256

となります。
5 値 (-2,-1,0,+1,+2) を取るビブラート LFO 出力を VIB_LFO とし、ビブラート処理後の FNUM を FNUM(V) と表現すると、(DVB = 1 での) ビブラート処理は、

FNUM(V) = FNUM + ((VIB_LFO * FNUM) >> 8)

と表現できます。
VIB_LFO = +2 の場合の周波数比 258/256 = 1.0078 をセント単位に換算すると、約 13.5 セントで、OPL4 アプリケーション・マニュアルに記載の 14 セントとほぼ一致します。
ビット・シフトで FNUM の LSB よりも右側になった値は単に切り捨てられるので、FNUM の値によってはビブラートがかからない状態になります。
音源パラメタを

FNUM = 0x080 (b7 のみ 1)
BLOCK = 7 (最上位オクターブ)
MULT = 8 (8 倍)

に変更した場合のスペクトラムを下に示します。

最初の例と比べて、FNUM の値を半分にして MULT の倍率を 2 倍にしているので中心周波数が 6 kHz であることは変化がありません。
しかし、VIB_LFO = ±1 の場合に 8 ビット・シフトにより元の FNUM の b7 だった 1 がシフト・アウトされてしまい、周波数の変化がゼロとなります。

したがって、VIB_LFO = ±2 の部分だけ周波数が変化し、VIB_LFO = 0 および VIB_LFO = ±1 の部分は周波数が変化していません。
同様にして、DVB = 0 (ビブラート「弱」、7 セント) の場合も調べたものをまとめると左の図のようになります。
「09」、「08」などは元の FNUM のビット位置を示しています。 DVB、VIB_LFO の値により左の図のような位置まで右シフトして、元の FNUM と加減算を行います。


式で表現すると、

FNUM(V) = FNUM + ((VIB_LFO * FNUM) >> (9 - DVB))

ここで、VIB_LFO の値は 0, ±1, ±2 に限られるので「乗算」ではなく「ビット・シフト」で実現でき、ビット・シフト部分に繰り込むことができます。 式としては乗算で書いた方が簡単なため、そうしています。
また、実際には 2 の補数表現のマイナスの数をいくら右シフトしても「ゼロ」にはならず、「マイナス 1」が残り続けるので、正確には、

FNUM(V) = FNUM + sgn(VIB_LFO)*((abs(VIB_LFO) * FNUM) >> (9 - DVB))

となります。 ここで sgn() は符号の正負により -1 あるいは +1 を返す関数、abs() は絶対値を返す関数とします。
ここで FNUM の値が 0x3f9 以上であると、ビブラート適用後の F-Number の値が 10 ビットで表現できる 0x3ff を超える、11 ビット幅の 0x400 〜 0x406 になる可能性が生じます。 
実際のチップでは、このような条件の場合でも破綻は見られないので、元は合計 10 ビット幅のレジスタ値 FNUM に対してビブラートを適用した後は、11 ビット幅を持つデータとして扱っていると思われます。