FPGA 版 FM 音源 (9) -- YMF262 測定 (1)

いい忘れていましたが、前回の YMF262 回路では、ひとつのシリアル・データ信号線に 1 サンプリング周期内に 2 チャンネル分が出力されるデータのうち、1 チャンネル分だけをマイコン側でキャプチャしています。
SPI のスレーブ・セレクト信号を真面目に作成すれば、2 チャンネル分のデータをキャプチャすることは可能ですが、回路の簡単化のため 1 チャンネル分にとどめています。
「音源」として実用するのではなく、あくまでも実験のためであり、YMF262 で出力可能な 4 チャンネルすべてに同じデータを出力する設定で使うので、複数チャンネルをキャプチャできても特にメリットはありません。
S/PDIFDAC へは、ステレオの L ch / R ch それぞれに、キャプチャした同じ「モノラル」データを格納して送り出しています。
現在、エンベロープではなく、「波形」を中心に実験していますが、次のようなことが分かってきました。

  1. スロット (オペレータ) 出力値は 13 ビット幅

  2. サイン波 ROM は 1 周期 1024 エントリ

  3. サイン波 ROM の出力 lb 値は整数部 4 ビット、小数部 8 ビット (0.0234 dB ステップ)

  4. サイン波 ROM のサンプリング位相は「位相 = 0」および「位相 = π/2」を含まず、サンプリング・ポイントの中間に位相ゼロの点があるタイプ

  5. スロット (オペレータ) 出力部の「符号 + 絶対値」表現から 2 の補数リニア値への変換は、おそらく次のような手順で行われており、スロットあたり -0.5 LSB の直流オフセットがある

    • まず絶対値の対数を、16 ビット幅より下位に何ビットか拡張した表現の正のリニア値に変換、
    • 符号が負なら 2 の補数をとる
    • 下位ビットを切り捨て、16 ビット幅に合わせる

  6. 「強制ゼロ」を含む波形 (WS = 1, 3, 4, 5) では、強制ゼロの部分にはオフセット (-0.5 LSB) を含まず、完全にゼロになる

  7. WS = 7 の波形は、下の式で表されるようなエクスポネンシャルで減衰する波形

\qquad\qquad\qquad {\rm sgn}(x) \cdot \exp( -a \cdot | x |)
(4) 番のサイン波 ROM のサンプリング位相は、下の図で赤い線で示したような位置になっています。

「位相 = 0」および「位相 = π/2」はサンプリング・ポイントに含まれず、図では青い線で示してある、サンプリング・ポイント間の中間の位置にあります。
「対数値」を出力とするテーブルでは、「ゼロ」の対数は「無限大」になってしまうので、通常の値としては格納できず、特別扱いをする必要があります。
「ゼロ」を含まなければ、すべての値は「普通」の値となり、テーブルにそのまま格納できます。
以前の記事 (→こちら) で触れたように、位相ゼロを含まないタイプのサイン波 ROM では少し処理が簡単になります。
FPGA で実現する場合には、利用可能なリソースの制限にかからなければ、演算ビット数や ROM 容量をケチる必要はありませんが、ソフトウェアで実現する場合には、演算ビット数や ROM 容量は計算速度に影響してきます。
今回 OPL3 チップでのインプリメントの状況が分かったので、これを「下限」として、FM 音源プログラムでの処理を見直して、演算速度を向上させようと考えています。
2 オペレータ・モードの「並列」アルゴリズムで、フルスケールのサイン波 (WS = 0) を発生させた場合のゼロクロス点付近の出力を、波形編集ソフトで拡大して表示したものを下に示します。

緑色の点がサンプル値そのもので、緑の曲線はそれを補間して表示したものです。
プラス側の値は +24、マイナス側の値は -26 で、-1 LSB の直流オフセットが乗っています。
2 オペレータの並列アルゴリズムでは、オペレータふたつとも「キャリア」となって、出力が加算されますから、1個あたり -0.5 LSB のオフセットが 2 個分で -1 LSB となり、計算は合っています。
プラス側のピークは +8168、マイナス側のピークは -8170 で、直流オフセット -1 を差し引き、オペレータ 1 個当たりの振幅に変換すると、4084.5 になります。
これは、ほぼ 13 ビット整数 +4095 〜 -4096 のレンジ一杯となっています。
オペレータ 8 個分で、ほぼ 16 ビット整数いっぱいのレンジとなりますから、2 オペレータ・モードの音を 4 音分重ねて、同様にゼロクロス点付近を拡大したのが次の図です。

4 音分を重ねているので、各音のキーオンを同時にすることはできず、それぞれ、ちょうど 1 サンプル分ずつの遅れがあるので、4 サンプル周期かかって波形が立ち上がっています。
プラス側の値は +96、マイナス側の値は -100 で、-4 LSB の直流オフセットが乗っており、これも計算通りです。
プラス側のピークは +32672、マイナス側のピークは -32680 で、直流オフセット -4 を差し引き、振幅を求めると、32676 になります。
このようにして WS = 0 と、WS = 7 の波形をキャプチャし、キーオンの「バラつき」が問題にならない位置で「デシメーション」して、1 周期 1024 サンプルの波形として表示したのが次の図です。

WS = 0 のサイン波形で、振幅 32768 を 0 dB として lb 値を求め、256 倍してプロットしたのが下の図です。 ちなみに、「lb 値」とは、2 を底とする対数を取り、符号を反対にしたものです。

これを見ると、lb を 256 倍した値が、ほぼ整数値を取っていることが分かります。
ぴったり整数値となっていないのは、リニア値が 16 ビット整数に量子化されている影響だと思われます。
lb を 256 倍した値が、整数値になるということは、もとの lb 値で見れば小数部 8 ビットまでが出力されていることになります。
同様に WS = 7 の波形について lb 値を求めプロットしたものを下に示します。

赤色の線がキャプチャした波形データから計算したもので、lb 値の大きい部分、つまり振幅の小さい部分で階段状になり、また lb = 13 で頭打ちになっているのは、出力値が 13 ビット整数のレンジに制限されているためです。
青色の線は、サンプル・インデックスが 128 以下のデータを使って求めた回帰直線です。
見て分かるように、きわめて良く直線に一致しています。
「片対数グラフ」で直線ということは、実際の値としてはエクスポネンシャルの関係となり、式で表せば、x をサンプル・インデックスとして、
\qquad\qquad \begin{cases} \, 2 ^{-x / 32} & , \, x > 0\\ \, \\ -2 ^{x / 32} & , \, x < 0 \end{cases}
となります。
この特性は、フェーズ・ジェネレータの位相値をサイン波 ROM を経由せず、「バイパス」して、そのまま lb 値として扱うことで実現できます。
出力データをキャプチャする際に直流が通らないと「サグ」により波形が変形してしまい、特に振幅の小さい部分で誤差が大きくなりますが、今回は 1 ビットも変化せずにキャプチャできているので、非常に明確な結果が得られました。