雲仙用のドッグ・フードを食べる企画として作成しているシンセサイザー。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; }