任意周波数の正弦波を生成するにはどうすればいいでしょう。周波数が指定されるたびにウェーブテーブルを作り直す方法はすぐ思いつきますが、実際にはよいアイデアではありません。任意周波数の正弦波の生成にはいろいろ手法があるようですが、簡単で実用的なものは与えられた小規模のウェーブテーブルから任意の周波数の波形を生成するというものです。その代表的なアルゴリズムとしてDDS ( Direct Digital Synthesizer )があります。以下ではDDSを使った正弦波生成について説明します。
DDSの動作は非常に簡単です(図1)。あらかじめ設定された周波数情報がレジスタに格納されており、単位時間ごとにアキュームレーターに加算されます。これによってアキュームレーターには瞬時の位相が格納されることになります。アキュームレーターの値が更新されるたびにウェーブテーブルを引いて位相を信号の値に変換します。これで設定した周波数の正弦波を生成できます。
図1 DDSの原理
DDSはアルゴリズムとしてよりも回路として有名です。この原理の回路は古くから知られ、現在ではワンチップ化されて数百MHzのサンプリングクロックで動作する製品が販売されています。
PLLと比較したDDSの特徴は次のようになります。
ソフトウェアによるDDSはハードウェアによるものよりぐっと周波数が落ちますが、数百KHzの信号ならば問題なく作ることができますし、注意深く最適化すればADSP-2191でも数MHzの信号を作ることができます。
DDSの動作を確認するための簡易クラスを作りました。CxHzはその名のとおり周波数xHzの信号を生成します。このクラスはC1KHzの子のクラスで、C1KHzが内蔵している1KHz用ウェーブテーブルを使って任意周波数を生成します。周波数は1KHzより低いものを単位Hzでコンストラクタ呼び出し時に指定します。
//************************************************************************** // // CxHz 型クラスの導出 // // CxHz型はC1KHzから導出する。C1KHzの正弦波テーブルを使って1KHz以下の // 任意周波数を生成する。 // //************************************************************************** class CxHz: public C1KHz { private: int phase; int index; int freq; public: CxHz( int frequency ); virtual void handleBuffer( struct mcm::sample * bufTx, struct mcm::sample * bufRx ); };
C1KHzでは1サンプル毎にウエーブテーブルの要素をコピーして1KHzを生成しました。CxHzでは代わりに図1のDDSの位相アキュームレーターを使ってウェーブテーブルにアクセスします。具体的には内部変数phaseとindexがアキュームレーターの役割を担います。phaseはアキュームレーターの下位でウエーブテーブルにアクセスするのは上位のindexです。phaseには毎回周波数freqが足され、1000を超えるとindexに桁上げがおきます。
//************************************************************************** // // CxHz::CxHzの定義 // // 内部位相変数とウェーブテーブルのインデックスを初期化する。 // また、与えられた引数を周波数として保存する(単位Hz) // //************************************************************************** CxHz::CxHz( int frequency ) { phase = 0; index = 0; freq = frequency; } //************************************************************************** // // CxHz::CxHzの定義 // // 内部位相に周波数を加える。1000を超えるとインデックスを進める。 // これにより、1KHzのテーブルを元に任意周波数を生成することができる。 // //************************************************************************** void CxHz::handleBuffer( struct mcm::sample * bufTx, struct mcm::sample * bufRx ) { for ( int i=0; i< this->bufSize; i++ ){ // bufSizeはバッファの長さ bufTx[i].l = bufTx[i].r = wave[index]; // 正弦波をおく phase += freq; // 位相を進める if ( phase >= 1000 ) { // 桁上げはおきたか? phase -= 1000; // 桁上げがおきたら補正 index ++; // 桁上げ分インクリメント if ( index >= 48 ) // 必要ならば index -= 48; // ラップアラウンド } } }
CxHzはちゃんと所望の周波数の信号を生成しますが、音を聞いてみると1KHzのときと違って相当にごった音になります。この原因は次で解明します。
次は⇒DDSの位相誤差