FM音源プログラム (18) -- オペレータ (11)

フェーズアキュムレータから 14 ビット分を抽出して、下の図のように b13 を「S」ビット、b12 を「D」ビットと呼ぶことにします。

「S」は Sign、つまり符号の意味で、S=1 の場合に出力の符号を反転します。
オペレータとしては、サイン波テーブル出力と EG 出力を乗算したものが最終結果となりますから、テーブル出力は正のまま EG 出力と乗算し、最終結果を得てから符号を反転してもかまいません。
「D」は Direction、つまり方向の意味で、D=0 の場合は順方向、D=1 の場合は逆方向にテーブルをアクセスします。
インデクスの処理としては、まず、「S」ビットを落として、0x0000 → 0x1FFF を2回繰り返す形にします。
もちろん、「S」ビットは結果の符号を決定するために必要ですから、「S」ビットの値を、どこかに記憶しておくか、あるいは、元のフェーズアキュムレータの当該ビットを符号決定時に参照します。
その後の、折り返し処理を、前回の 1. の位相ゼロ点を含むタイプのテーブルの場合について表であらわすと、

   入力    出力
0x0000 → 0x0FFF 0x0000 → 0x0FFF
0x1000 → 0x1FFF 0x1000 → 0x0001

となります。
プログラムの断片としては、たとえば、次のようになります。

#define SPOS (13)
#define DPOS (12)
#define SMASK (1 << SPOS)
#define DMASK (1 << DPOS)
#define IXMASK (SMASK - 1)

int16_t op( ... )
{
  uint16_t phh, ix;
  int16_t s0, opout;
  ...
  phh = (phacc + phmod) >> 16; // phase acc high
  ix = phh & IXMASK;           // extract index
  if (DMASK & ix) ix = SMASK - ix; // backword 
  s0 = stab[ix];           // indexing sin table
  opout = ((int32_t)s0 * egout) >> 15; // mult by EGout
  return ((SMASK & phh) ? -opout : opout);
} // int16_t op()

フェーズアキュムレータは N=30 を仮定していて、上位 16 ビットを取り出せばテーブルインデクスとしてのビット位置が合います。
変数「phh」の名前は phase accumulator の high ワードといった意味合いです。
「phh」の「S」ビット以上の位置のビットを落として、変数「ix」に格納します。
if 文により「D」ビットを調べ、「1」であれば SMASK の値 0x2000 から ix を引いて、逆方向のインデクスにします。*1
条件分岐なので、多くの 32 ビット RISC プロセッサでは、パイプラインが乱れて実行時間の面では不利になります。
変数「egout」は EG および他の要素により決定されるオペレータ出力レベルを表すもので、オペレータ関数の外部で値が設定されているものとします。
最後の return 文のところで、最終的な符号を決定しています。 
これも条件分岐ですから、実行時間の点では不利です。 +1 あるいは -1 を乗算する形にすれば条件分岐をなくすことができます。
前回の 2. の位相ゼロを含まないタイプでは、上のプログラムの if 文のところを

  if (DMASK & ix) ix ^= IXMASK; // backword 

とします。*2
実際のFM音源プログラムでは、処理速度を少しでも上げるため、1/2 周期のサイン波テーブル (8K + 1 = 8193 エントリ) を使って、正負に関する対称性だけを利用しています。

*1:実質的には 2 の補数を求めていることになります。

*2:1 の補数を取っています。