雲仙フレームワークの試食としてのシンセサイザー作成。前回までで概ね音周りは形になりました。
今回はUI周りです。鍵盤を押す度にVFOとフィルタの周波数を変更して簡易オルガンとして使えるようにします。
割り込み禁止処理
UIを提供するボタンやボリュームの読み込み、LEDへの書き込みはすべてmain()関数の中で行います。main()はmbed SDKの実行コンテキストですので、自由にmbed SDKのクラス・ライブラリを使うことが出来ます。UIから受け取った情報をSignalProcessing型オブジェクトに書き込むのも、このコンテキストの中です。
一方で、SiganlProcessing型の信号処理本体はrun()メソッドに実装されており、このメソッドは割り込みコンテキストの中から呼ばれます。
run()メソッドはmain()から渡されたパラメタを元に音を組み立てますが、このパラメタの書き込みは上記の通り、main()の実行コンテキストです。そのため、パラメタの書き込み作業が割り込みによって中断し、パラメタの書き込み途中で信号処理が行われる可能性があります。
このような割り込みによる中断が問題になるかならないかは、処理によります。一般にパラメタの書き込みがワード単位であり、パラメタをばらばらに更新しても問題ないようなプログラムでは、中断は問題になりません。しかし、大きなパラメタを分割して書き込んだり、複数のパラメタが一貫性を維持していなければならないような場合には、割り込みを禁止して安全区間を作り上げてパラメタを渡す必要があります。
本シンセサイザーは前者の安全な例に属しますが、なにぶんドッグフードを食べている最中ですので割り込み禁止処理を実装しました。割り込み禁止はスケルトンによってSignalProcessing型のメソッドして提供されており、これらを呼ぶだけで安全な引数の書き込みが可能になります。
enter_critical_section()とleave_critical_section()に囲まれた範囲が割り込み禁止の安全区間になります。
// 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.
}
メイン・ループ
メインループではひたすら鍵盤の状態を読んで、対応する周波数の設定と、鍵盤状態の書き込みを行っています。ukifune基板のアンチ・チャタリングは回路としては失敗作ですが、メイン・ループにある待ち時間のおかげでチャタリングの影響は消えています。アンチ・チャタリング回路のキャパシタは実装しなくても良いでしょう。
なお、鍵盤状態の読み込みと処理は繰り返しがしつこいため、マクロを使用しています。
#define KEYCTRL( SWITCH, FREQ )\
if ( pushing & ( 1 << ukifune::SWITCH ) )\
{\
process->set_vfo_frequency(FREQ);\
process->eg_on();\
}\
else if ( releasing & ( 1 << ukifune::SWITCH ) )\
process->eg_off();
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) );
process->set_filter_f_factor( ukifune::get_volume(2) );
process->set_filter_Q( ukifune::get_volume(3) );
process->set_eg_attack( ukifune::get_volume(4) ); // A
process->set_eg_decay( ukifune::get_volume(5) ); // D
process->set_eg_sustain( ukifune::get_volume(6) ); // S
process->set_eg_release( ukifune::get_volume(7) ); // R
// sample usage of button switch detection
ukifune::get_button_state( pushing, releasing, holding);
// pushing detection demo
// waveform
if ( pushing & (1 << ukifune::swm1 ) ) // is SWM1 switch pusshing down?
if ( form == triangle )
{
form = square;
process->set_vfo_wave_form( form );
ukifune::turn_led_off( ukifune::led1_1 );
ukifune::turn_led_on( ukifune::led1_2 );
}
else
{
form = triangle;
process->set_vfo_wave_form( form );
ukifune::turn_led_on( ukifune::led1_1 );
ukifune::turn_led_off( ukifune::led1_2 );
}
// filter mode
if ( pushing & (1 << ukifune::swm2 ) ) // is SWM2 switch pusshing down?
if ( filter_mode == lpf )
{
filter_mode = bpf;
process->set_filter_mode( filter_mode );
ukifune::turn_led_off( ukifune::led2_1 );
ukifune::turn_led_on( ukifune::led2_2 );
ukifune::turn_led_off( ukifune::led2_3 );
ukifune::turn_led_off( ukifune::led2_4 );
}
else if ( filter_mode == bpf )
{
filter_mode = hpf;
process->set_filter_mode( filter_mode );
ukifune::turn_led_off( ukifune::led2_1 );
ukifune::turn_led_off( ukifune::led2_2 );
ukifune::turn_led_on( ukifune::led2_3 );
ukifune::turn_led_off( ukifune::led2_4 );
}
else // hpf
{
filter_mode = lpf;
process->set_filter_mode( filter_mode );
ukifune::turn_led_on( ukifune::led2_1 );
ukifune::turn_led_off( ukifune::led2_2 );
ukifune::turn_led_off( ukifune::led2_3 );
ukifune::turn_led_off( ukifune::led2_4 );
}
// Key board control
KEYCTRL( swk1, 261.626 )
KEYCTRL( swk2, 277.183 )
KEYCTRL( swk3, 293.665 )
KEYCTRL( swk4, 311.123 )
KEYCTRL( swk5, 329.628 )
KEYCTRL( swk6, 349.228 )
KEYCTRL( swk7, 369.994 )
KEYCTRL( swk8, 391.995 )
KEYCTRL( swk9, 415.305 )
KEYCTRL( swk10, 440.000 )
KEYCTRL( swk11, 466.164 )
KEYCTRL( swk12, 498.883 )
KEYCTRL( swk13, 523.251 )
// you have to call tick() every 20mS-50mS if you need get_volume()
wait(0.05);
ukifune::tick();
}