FM音源プログラム (3) -- EG (3)
乗算によらない減衰エンベロープ
ディケイおよびリリースの、エクスポネンシャルで減衰するエンベロープは、減衰が急激でない場合には、乗算なしで、シフトと減算だけで実現できます。
ディケイ・レートを として、連続時間でのディケイ・エンベロープの関数を とします。
から順にサンプリング周期 でサンプリングした離散値を ( は正の整数) とすると、 で表されます。
時刻 での値から時刻 での値を、連続時間の式で、一次近似で求めると、
となります。 したがって、サンプリングされた値では、
と表されます。
現在の値 に 1 より小さい値 をかければ、次のサンプリング・タイミングでの値 が求まることになります。
いま、 を正の整数として、 と表される場合を考えます。
と との乗算は、単に を右に ビットシフトする操作で実現できます。
C 言語の式で表現すると、
y_n -= (y_n >> s);
でエンベロープ値をアップデートできます。
もちろん、この方法では任意のレート に対応できるわけではなく、 という前提を満たす場合に限られ、そのレートは 2 倍ごと、あるいは 1/2 倍ごとにしか変化できません。
また、 が小さい時、つまり変化が急な場合に誤差が大きくなります。
下の図は、この方法で生成したエンベロープです。 を 1 から 8 まで変化させてプロットしてあり、最も変化の速いのが です。
この方法は、ハードウェア乗算命令を持たない 8 ビットマイコンなどに適していますが、長いディケイ・タイムを実現するためには、 のビット幅が大きくなりがちで、シフト量も多くなりがちです。
たとえば、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 イベント (vol_lb)
- マスター・ボリューム (システムエクスクルーシブ)
- チャンネル・ボリューム (CC#7)
- エクスプレッション (CC#11)
各項目の後に (tl_lb) のように表記してあるのは、イベントの分類別に値を計算して格納しておく変数名です。