FM音源プログラム (3) -- EG (3)

乗算によらない減衰エンベロープ

ディケイおよびリリースの、エクスポネンシャルで減衰するエンベロープは、減衰が急激でない場合には、乗算なしで、シフトと減算だけで実現できます。
ディケイ・レートを R として、連続時間でのディケイ・エンベロープの関数を f(t) = \exp(-R\cdot t) とします。
t=0 から順にサンプリング周期 T でサンプリングした離散値を y_n (n は正の整数) とすると、y_n = f(n \cdot T) で表されます。
時刻 t での値から時刻 t + T での値を、連続時間の式で、一次近似で求めると、
\qquad f(t+T) = f(t) + T\cdot \frac{d}{dt}f(t) = (1-T \cdot R) \cdot f(t)
となります。 したがって、サンプリングされた値では、
\qquad y_{n+1} = (1-T \cdot R)\cdot y_n
と表されます。
現在の値 y_n に 1 より小さい値 (1-T \cdot R) をかければ、次のサンプリング・タイミングでの値 y_{n+1} が求まることになります。
いま、s を正の整数として、T \cdot R = 2^{-s} と表される場合を考えます。
\qquad (1-2^{-s}) \cdot y_n = y_n - 2^{-s} \cdot y_n
y_n2^{-s} との乗算は、単に y_n を右に s ビットシフトする操作で実現できます。
C 言語の式で表現すると、

y_n -= (y_n >> s);

エンベロープ値をアップデートできます。
もちろん、この方法では任意のレート R に対応できるわけではなく、T \cdot R = 2^{-s} という前提を満たす場合に限られ、そのレートは 2 倍ごと、あるいは 1/2 倍ごとにしか変化できません。
また、s が小さい時、つまり変化が急な場合に誤差が大きくなります。
下の図は、この方法で生成したエンベロープです。 s を 1 から 8 まで変化させてプロットしてあり、最も変化の速いのが s=1 です。

この方法は、ハードウェア乗算命令を持たない 8 ビットマイコンなどに適していますが、長いディケイ・タイムを実現するためには、y_n のビット幅が大きくなりがちで、シフト量も多くなりがちです。
たとえば、4 バイト (32 ビット) のデータを 16 ビットシフトする場合を考えます。
そのマイコンにバイトデータの 1 ビットシフト命令しかなければ、ビットシフト命令を 4 × 16 = 64 回実行する必要があり、必ずしも高速とはいえません。
サンプリング周期を変化させることが可能なら 2 倍ステップより細かくレートを変えられます。 サンプリング周期を変化させられない場合は、シフトだけでは無理で、乗算が必要になります。
FM音源プログラムでも、初期にはこの方法を使っていたのですが、固定サンプリング周期の構成のため、次に説明する、lb (対数) ドメインで計算して、リニア数値に変換する方式に変えました。

lb (対数) ドメインでの計算

オペレータの出力レベルは EG ばかりではなく、AM などにも影響されます。
これらを素直に実現すれば、オペレータの正弦波出力に EG の値を掛け、さらに AM の振幅を掛け、チャンネル・ボリュームを掛け、各チャンネル出力を加算するといった流れになります。
この方法では、何回も乗算をする必要があり、乗算命令の処理時間が問題になります。
そこで、リニアな値の対数である lb 値のドメインで各種の計算を行い、最後にリニア値に変換し、乗算を1回だけ行う方式にしました。
真数での乗算は対数では加算になりますから、処理時間は短くて済みます。
EG の処理としては、ディケイ/リリースではエクスポネンシャルな減衰が必要です。
lb 値での表現をサンプリング・タイミングごとに、単に直線的に増加させていくだけで、lb - リニア変換後にエクスポネンシャルな減衰特性が得られます。
本来、MIDI チャンネル・ボリューム (コントロール・チェンジ CC#7) やエクスプレッション (CC#11) は各チャンネル出力ごとに乗算して、ミックスすべきものですが、ここでは、これらの寄与も EG 計算の中に入れてしまって、乗算回数をチャンネル当り1回におさえることにします。

イベントのライフサイクルによる分類

EG に限らず、オペレータ出力レベルに影響を与えるイベントの「ライフサイクル」と言うと大げさですが、発生タイミング等について分類します。

  • MIDI ノートオン時に確定し、次のノートオンまで不変 (tl_lb)
    • TL (Total Level)
    • KSL (Level Key Scale)
    • MIDI ノートオン・ベロシティ
  • 任意のタイミングで発生する MIDI イベント (vol_lb)
    • マスター・ボリューム (システムエクスクルーシブ)
    • チャンネル・ボリューム (CC#7)
    • エクスプレッション (CC#11)

各項目の後に (tl_lb) のように表記してあるのは、イベントの分類別に値を計算して格納しておく変数名です。