前回はやっつけでVFOクラスを作りましたが、今回は作ったVFOを雲仙に組み込みます。もともとは「ドッグフードを食べる」ことで雲仙を鍛えるのが目的の企画ですので、どちらかというと前回より今回の方が本筋です。
Monophonicクラス
雲仙にVFOを組み込む前に、まずはMonophonicクラスを作ってここにVFOを組み込みます。Monophonicクラスは短音のシンセサイザー・クラスで、この中にEGやフィルタと言ったモジュールを組み込んで、独立したシンセサイザーとして作り上げます。最終的にCPU負荷に余裕があればポリフォニック・シンセサイザーに仕上げたいところですが、どうなるでしょうか。
クラスの宣言は以下のようになっています。実装はおもしろみがありませんので特にこの稿では掘り下げませんが、ソースコードは公開しています。ソースコードはこの後どんどん変っていきますのであしからず。
// Monophonic synthsizer class
class Monophonic {
public:
Monophonic( unsigned int block_size );
virtual ~Monophonic(void);
void run(
float out_buffer[], // place to write the right output samples
unsigned int block_size // block size [sample]
);
void set_Fs( int Fs ); // unit is Hz.
void set_vfo_frequency( int freq ); // unit is Hz.
void set_vfo_duty_cycle( float duty ); // 0 ... 0.5
void set_vfo_wave_form( wave_form form );
private:
VFO *vfo;
};
SignalProcessingクラスの宣言
SignalProcessingクラスは雲仙フレームワークではなく、スケルトンが提供するクラスです。このクラスはスケルトンの中でユーザー・アルゴリズムの実装場所となっています。
スケルトンが提供しているSignalProcessingクラスの宣言の内、コンストラクタ、run()メソッド、enter_critical_secion()メソッド、leave_critical_section()メソッドは、インターフェースを変えてはいけません。一方で、その他のメソッドやメンバー変数に関しては変更は自由です。また、好きなメソッド、メンバー変数を追加して結構です。
なお、publicなメソッドを増やす場合には、雲仙の管理プログラムからアクセスする場合に限ると良いでしょう。これについては後述します。
今回の実装では、Fsの変更、波形の変更、デューティサイクルの変更などを行うシンセサイザーらしいメソッドが追加されています。なお、これらのメソッドは作り込んでいくうちに変更が加わりますので気をつけてください。
// User Signal processing Class
class SignalProcessing {
public:
// essential members. Do not touch
SignalProcessing( unsigned int block_size );
void run(
float rx_left_buffer[], // array of the left input samples
float rx_right_buffer[], // array of the right input samples
float tx_left_buffer[], // place to write the left output samples
float tx_right_buffer[], // place to write the right output samples
unsigned int block_size // block size [sample]
);
// project depenedent members.
void set_volume( float vol );
void set_Fs( int Fs ); // unit is Hz.
void set_vfo_frequency( int freq ); // unit is Hz.
void set_vfo_duty_cycle( float duty ); // 0 ... 0.5
void set_vfo_wave_form( wave_form form );
private:
// essential members. Do not touch.
void enter_critical_section(void);
void leave_critical_section(void);
// project dependent members.
float volume_level; // 0 ... 1.0
Monophonic * note;
};
SignalProcessingクラスの実装
クラスへの実装のうち、目立つところとしてrun()メソッドを取り上げます。
このメソッドは雲仙のprocessコールバックから呼び出されます。引数としてはオーディオ・コーデックLSIからの受信信号、オーディオ・コーデックLSIへの送信信号、ブロックサイズがあります。送受信信号はそれぞれステレオです。値は浮動小数点型であり、-1.0から+1.0の範囲を持ちます。
メソッドの中ではVFO(を中に持つMonophonicクラス型メンバー変数変数note)のrun()メソッドを呼んでいます。なお、その出力はモノラルなので、ボリュームを反映した後にステレオになるようコピーしています。
// Modify this method to implement your audio algorithm.
void SignalProcessing::run(
float rx_left_buffer[], // array of the left input samples
float rx_right_buffer[], // array of the right input samples
float tx_left_buffer[], // place to write the left output samples
float tx_right_buffer[], // place to write the right output samples
unsigned int block_size // block size [sample]
)
{
// place the signal processing coce here
// VFO
this->note->run( tx_left_buffer, block_size);
// apply gain and copy to right ch.
for ( int i= 0; i< block_size; i++ )
{
tx_right_buffer[i] = tx_left_buffer[i] *= this->volume_level;
}
} // End of run()
なお、管理プログラムとの間の通信に用いられるメソッドは、すべてクリティカル・セクションとして保護しています。
// Sampling Frequency
void SignalProcessing::set_Fs( int Fs )
{
this->enter_critical_section(); // forbidden interrrupt.
this->note->set_Fs( Fs );
this->leave_critical_section(); // now, ok to accept interrupt.
}
管理プログラムの実装
main()には信号処理の管理プログラムを実装します。
ここでは周期的にヴォリュームの値を読み、それをSignalProcessing型変数processに適用するということを繰り返しています。また、モード・スイッチを監視し、推される度に波形を三角波と矩形波の間で切り替えることもしています。
/*========================= Main program. ====================================*/
int main()
{
uint32_t pushing, releasing, holding;
// VFO form
wave_form form = triangle;
// start audio. Do not touch
initialize_system();
process->set_vfo_frequency( 440 );
process->set_vfo_wave_form( form );
ukifune::turn_led_off( ukifune::led1_1 );
ukifune::turn_led_on( ukifune::led1_2 );
// main loop. Signal processing is done in background.
while(1)
{ // place your foreground program here.
// get volume from UI panel, then apply it to signal processing.
process->set_volume( ukifune::get_volume(0) );
process->set_vfo_duty_cycle( ukifune::get_volume(1) );
// sample usage of button switch detection
ukifune::get_button_state( pushing, releasing, holding);
// pushing detection demo
if ( pushing & (1 << ukifune::swm1 ) ) // is SWM1 switch pusshing down?
if ( form == triangle )
{
form = square;
process->set_vfo_wave_form( form );
ukifune::turn_led_on( ukifune::led1_1 );
ukifune::turn_led_off( ukifune::led1_2 );
}
else
{
form = triangle;
process->set_vfo_wave_form( form );
ukifune::turn_led_off( ukifune::led1_1 );
ukifune::turn_led_on( ukifune::led1_2 );
}
// you have to call tick() every 20mS-50mS if you need get_volume()
wait(0.05);
ukifune::tick();
}
} // End of main