任意周波数の正弦波を生成するにはどうすればいいでしょう。周波数が指定されるたびにウェーブテーブルを作り直す方法はすぐ思いつきますが、実際にはよいアイデアではありません。任意周波数の正弦波の生成にはいろいろ手法があるようですが、簡単で実用的なものは与えられた小規模のウェーブテーブルから任意の周波数の波形を生成するというものです。その代表的なアルゴリズムとして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の位相誤差