シンセサイザー用に状態変数フィルタを実装しました。
このフィルタはコントロール周波数およびQを独立に操作することが可能で、かつLPF、HPF、BPFとして同時に利用することができます。
状態変数フィルタ
あまり深いところまでは突っ込みませんが、状態変数フィルタは二段の積分機からフィードバックをかけたフィルタになっており、オペアンプによる回路が有名です。フィードバック信号は二箇所から取り出します。
積分機はLPFです。したがって、LPFの出力を負帰還ループの帰還パスとして使うと、LPFの入力は自動的にHPF信号となります。状態変数フィルタでは二段の積分機を使うことで、LPF,HPF,BPFを作り出しています。BEFも作ることができますが、遅延の調整が必要です。
クラス宣言
クラス宣言は以下の通りです。コントロール周波数 fc を設定するメソッドのほかに、f_factorなるパラメタをセットするメソッドを用意しました。これは、形式上のコントロール周波数を「起点」として定め、外部からこれを「振る」事ができるようにするためのものです。何を言っているか分からないかと思いますので例を挙げましょう。
たとえばVFO周波数を440Hzとしてフィルタのコントロール周波数も440Hzに設定したとしましょう。これに対してたとえばLPFのカットオフ周波数を振り回したいときにはset_fc()メソッドではなく、set_control_f()を動かします。あとでVFO周波数を880Hzに変更したときには、単にset_fc()メソッドを呼べば、先ほど「振った」加減はそのままにフィルタの周波数特性を変えることができます。
// State Variable Filter
// The f * f_factor must be < Fs/6.
class SVFilter : public amakusa::AbstractFilter {
public:
SVFilter( uint32_t a_block_size );
virtual void run( float *pSrc, float *pDst);
void set_Q( float Q ); // real Q factor [ 0.5 ... inf ]
void set_Fs( int new_Fs ); // Hz
void set_fc( int new_fc ); // Hz
void set_f_factor( float new_f_factor );
void set_mode( svf_mode new_mode );
private:
// internal variable
float d1, d2; // delay 1, delay 2;
float q, f; // q = 1/Q, f = 2 * sin( fc*f_factor*pi/Fs );
// parameter set by method
int Fs, fc; // sampling frequency and control frequency
float f_factor;
svf_mode mode; // lpf, hpf, bpf
void update_parameters( void );
};
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;
};
実装
実装には特に面白みはありません。状態変数フィルタのブロックダイアグラムをそのまま実装しています。
STM32F746のCORTEX-M7Fコアは浮動小数点演算が可能です。そのため、フィルタのピークによって信号振幅が1を超え、ピークのあるフィルタの実現が簡単です。
/************************************************************************
* +--------------------------------------> hp
* |
* | +--------------------> bp
* | f D1 | f
* x(n)->(+)-+-|>-(+)->[z^-1]->+-|>->(+)--------+---> lp
* A | | | |
* | +<----------+ +<-[z^-1]-+
* | -q | D2 |
* (+)<------<|----------+ |
* | -1 |
* +<-----------------<|-----------------+
*
* bp = D1
* lp = D1 * f + D2
* hp = x - q * bp - lp
*
* D1 = hp * f + D1
* D2 = lp
*
************************************************************************/
void SVFilter::run( float32_t *pSrc, float32_t *pDst )
{
float32_t bp, lp, hp;
for ( int i= 0; i<this->block_size; i++ )
{
// calc the filter
bp = this->d1;
lp = bp * this->f + this->d2;
hp = pSrc[i] - this->q * bp - lp;
// update delay
this->d1 += hp * this->f;
this->d2 = lp;
// mode dependent output
switch ( this->mode )
{
case lpf :
pDst[i] = lp;
break;
case hpf :
pDst[i] = hp;
break;
case bpf :
pDst[i] = bp;
break;
};
}
}