▼ 漸化式でsin波を高速に生成する方法
漸化式*1によりsin信号を生成する方法を紹介します。これにより、マイコンのような機器でも高速にsin信号を生成することができます。FPGA等にも容易に応用できます。少し工夫すると円も描けます。
※高精度の演算には使えません。
*1 : 基本演算のみの反復計算不要な式
■特徴
- 任意の周波数、任意のサンプリング周波数でsin波を生成できます。
- 初期値のみ、あらかじめ計算しプログラムに埋め込むか、別の方法で計算する必要があります。
- 2次のIIR型フィルタにより生成するため、時間が経つと計算誤差が累積します。
生成周波数/サンプリング周波数が整数ならば、誤差の累積は非常に小さくて済みますが、それだったらテーブルで良いような気もします(でもメモリの少ないマイコンなら多少は有利かな)。
■生成アルゴリズム
至って簡単です。
| 変数 | 解説 |
|---|---|
| y[n] | 生成系列 |
| f | 生成周波数[Hz] |
| k | サンプリング周波数[sample/sec] |
| phi | 初期の位相 |
| PI | 円周率 / 3.1415926535.... |
初期値計算。
y[0] = sin(phi); y[1] = sin(phi + 2*PI*f/k); ar1 = 2*cos(2*PI*f/k);
生成式(n>2)。
y[n] = ar1*y[n-1] - y[n-2];
誤差が累積します。float型でためしたところ、(パラメータにもよりますが)1000回の計算で0.00012程度、10000回の計算で0.0012程度の誤差が出ました。doubleで生成すれば相当低誤差で使えそうです。
もしfがkで割り切れるなら1周した段階で(予め計算してある)初期値に戻してあげる方が良いです。割り切れない場合は、(もしsinを計算可能な環境ならば)適当な回数を演算したところで初期値計算を再度行うと良いでしょう。
永遠に計算し続けると、誤差の累積により発散するか0に収束すると思われます。
■サンプル
Cのソース。
#define PI 3.14159265358979323846
float ar1,yz1,yz2;
sin_init(float freq, float sps, float phi) {
yz1 = sin(phi);
yz2 = sin(phi+2*PI*freq/sps);
ar1 = 2*cos(2*PI*freq/sps);
}
float sin_get() {
float g;
g = yz1;
yz1 = yz2;
yz2 = ar1*yz1 - g;
return g;
}
main() {
sin_init(1000, 10000, 0);
while(1)
printf("%f\n", sin_get());
}
■原理
2次IIRフィルタを振動させているだけです。Z平面上での極を単位円上に配置してパラメーターを算出しています。
右の図に示すようにIIRによる振動現象は、単位円上を動く点と捉えることができます*2。きまった角度φずつ単位円の上をスライドする点を観測して生成されるものがsin波です。*3図で全体を100sample/secとして、各ポイントに対応する周波数を書きました。左に回転量が増えるほど周波数が高くなります。*4
IIRフィルタでの回転量を決めるのは、右図に示した「極」です。IIRフィルタで計算すること*5は複素平面上で極を単純にかけ算することに等しいのです。複素数の授業でやったと思いますが、ある点に対して単位円上の点pをかけ算すると、その点は点pの角度だけ回転します。これが、IIRフィルタ(やFIRフィルタ、ARモデルやMAモデル)を貫く基本概念になります。
本当なら1次の極だけで足りるのですが、それですとIIRフィルタの係数が複素数になってややこしいので、一般には実軸対称の位置に(複素共役の位置に)もう1つ点を取ります。すると、2次の実パラメータを持つIIRフィルタが構成できます。
導出
フィルタの極p1は、周波数fとサンプリング周期kから次のように決まります。
φ = 2πf/k p1 = cos(φ) + jsin(φ)
もうひとつの極p2は実軸対称の位置に取りますから
p2 = cos(φ) - jsin(φ)
です。極が2つあるときのIIRフィルタ伝達関数は
となり、ここから真面目に計算すると、
IIR(z)の分母 = 1-p1p2(z^-1)+(z^-2) p1p2 = 2cos(φ)
が導かれます。これをIIRフィルタの定義式に戻すと、
になり、アルゴリズムの式が導かれます。
*2 : インパルスのZ変換がy(0)=1になり、これはちょうど右を向いた(1,0)信号と思うことができますが、あとの話と整合性を取ろうとすると解説がややこしくなるので深入りは避けます
*3 : 余談になりますが、複素辺面上をスライドする点を実軸から捉えてcos、虚軸から捉えてsinと考えると、虚数平面上(2次元の平面上でも同じ)の点を捉える角度によって信号が異なるということがわかります。どんな観測信号も1次元に落とし込まれる(より正確に言えば射影される)段階で同じことが起きています。
*4 : この図でエイリアシングと呼ばれる現象を理解することができます。例えば75Hzの信号をスライドする点として順にプロットしてみると、左に270度ずつ回転させている(75Hz相当)にも関わらず、まるで右に90度ずつ回転している(25Hz相当)ように見えます。
*5 : 畳み込み計算
■参考文献
- ディジタルフィルタとz変換(要Java)
Z平面や極がどのような影響を与えるか、どのような意味があるのかを、目で見て、手で触って掴むことができます。FIR/IIRフィルタやARモデルなどを考えるうえで非常にためになるサイトです。
- TB-URL http://nabe.blog.abk.nu/0306/tb/
-
▼
FFTって何だ? 〜FFTは真の周波数特性ではない〜
nabeの雑記帳 よくエンジニアの人と話をしていると、FFT=周波数特性と思っている人が居ます。それも絶対に正しい周波数特性と思っているいう人が居るんだけどそれは間違えです。なるべく数式を使わずに簡単にFFTとは何であるのかを解説します。 フーリエ変換とは フーリエ級数展開...




1: 菅野 2008年06月05日(木) 深夜0時15分
原理はサッパリでしたが
このアルゴリズムは簡単で便利ですね。使わせてもらいます!
2: なべ 2008年06月07日(土) 午後2時38分
使えれば原理なんていいんですよ(笑)
実際、この生成法は便利だと思うのでぜひ使ってください。
3: たぐっさん 2009年12月14日(月) 午後6時20分
こんにちは。初投稿です。素晴らしいサイトを見つけたので感動しています!
いろいろと参考にさせてもらってます。ありがとうございます!
さっそく質問なんですが、ここのページの「初期の位相」というのはラジアンで指定したらいいのでしょうか?もしcosを生成したかったら初期の位相にπ/2を代入すればいいのでしょうか?
4: なべ 2009年12月14日(月) 深夜0時26分
>「初期の位相」というのはラジアンで指定したらいいのでしょうか?
C言語の cos 関数は rad だから、ラジアンですね。
>cosを生成したかったら初期の位相にπ/2を代入すればいいのでしょうか?
Yes
5: たぐっさん 2009年12月15日(火) 午後5時48分
ありがとうございます。早速使ってみます。