雲仙用のドッグ・フードを食べる企画として作成しているシンセサイザー。Youtubeではすでに音が出る様子を公開していますが、ブログでは粛々と個々のモジュールを紹介していきます。
エンベロープ・ジェネレータは音の特徴を左右する重要なブロックです。ある意味シンセサイザーの顔とも言えるでしょう。VFOの周波数変調に使うやりかたもあるのですが、お試しシンセとしてひねらずに振幅制御に使うことにします。
クラス宣言
このシリーズを読んでいる方がどれだけいるかはわかりませんが、最初から読み続けている人なら、クラス宣言の形にはそろそろ想像がつくころあいでしょう。コンストラクタで処理ブロックのサイズを設定し、run()メソッドでは与えられたバッファに振幅データを格納して返しています。
amakusa::AbstractFilterから派生させても良かったのですが、気の迷いで周波数変調に使うことになった場合にそなえ、内部で余計なことをせずに包絡線のみ出力させています。
class EG {
public:
EG ( int32_t a_block_size );
virtual void run( float *pEnvelope );
void on(void);
void off(void);
void set_attack( float attack ); // [0,1.0]
void set_decay( float attack ); // [0,1.0]
void set_sustain( float sustain ); // [0,1.0]
void set_release ( float attack ); // [0,1.0]
private:
float current_level, sustain_level;
float attack_time_constant, decay_time_constant, release_time_constant;
eg_state state;
int32_t block_size;
};
実装
内部では、外部からのキーのオン、オフをトリガーにAttack, Decay, Release の間で状態遷移させています。唯一、Attackだけは包絡線が所定の値になったときに自動的にDecayへと状態遷移させています。
包絡線の曲線は1-exp(-At)とすべきですが、ここは手を抜いて1-A^tとしています(1.0>A)。
void EG::run( float32_t *pEnvelope )
{
if ( this->state == release )
for ( int i= 0; i< this->block_size; i++ )
pEnvelope[i] = this->current_level =
this->current_level * this->release_time_constant;
else if ( this->state == decay )
for ( int i= 0; i< this->block_size; i++ )
pEnvelope[i] = this->current_level =
( this->current_level - this->sustain_level ) * this->decay_time_constant
+ this->sustain_level;
else // attack
{
for ( int i= 0; i< this->block_size; i++ )
pEnvelope[i] = this->current_level =
1.5f -
( 1.5f - this->current_level ) * this->attack_time_constant;
if ( this->current_level >= 1.0f )
this->state = decay;
}
}
void EG::on(void)
{
this->state = attack;
}
void EG::off(void)
{
this->state = release;
}