Goertzel algorithm
酒飲みの為の、酒呑みによる、 「日本酒カレンダー」 を教えてもらったので謹んでメモしておく。
probe goertzel
前回やったCW信号のデコード。この核心は、goertzelというアルゴリズムだった。 単一周波数が存在するか否かを効率よく検出できるとな。これでもう、FFTとはおさらばできる 。
内部でどんな計算がおこなわれているか、プローブもとえgdbをあててみたくなった。そこで困るのが、信号がon/offしてる事。(CWなんであたりまえ)
そんな訳で、連続波が欲くなったんだ。確かsoxでも発生できたはず。sox 正弦波 発生 とか 叫んでみたよ。
$ sox -r 10000 -b 16 -c 1 -n sweep.wav synth 10 sine 100-3000 gain -10
こんな風にすると、100から3000Hzまで10秒かけて掃引しながら正弦波を発生してくれるとな。なんだか、これってGNU RadioにVCOって名前で登録されてたな。
gain A power gain in dB. Zero gives no gain; less than zero gives an
attenuation.
gainの-10って何かなと調べてみた。ここには説明ないけど、絶対基準は、CDのフルスケールの音量みたい。大体こんな大音量だとクリッピングしちゃうので、-3dB (~70%)が実用的らしい。
ああ、検索結果に、こんな楽しいコンテンツも出てきた。
作者さんは、オイラーと同郷のsakura村の方。オイラーよりは富豪な方で、FreeBSDをバリバリ使っておられた。
こうなると、同じような事を、やってみたくなるのが人情というものです。
あれ? Goertzelはどうした?
とりあえず、ワシントン大学の資料をみましょ。
work bench
どうも、上の掃引波形と言い、gnuplotによる表示機構といい、ネットワークアナライザーを作って実習しろと言う圧力があるな。
#include <stdio.h>
#include <math.h>
float sampling_freq=10000.0;
float target_freq= 600.0;
#define N 100 // BW = sampling_freq / N
short int Data[N];
float omega,coeff,mag2;
float Q0,Q1,Q2,sine,cosine;
int rc,i,k;
int main (){
k = (int) (0.5 + ((N * target_freq) / sampling_freq));
omega = (2.0 * M_PI * k) / N;
cosine = cos(omega);
coeff = 2.0 * cosine;
for (;;){
rc = fread(&Data, sizeof(short int), N, stdin);
if (rc < 1)
break;
Q1 = 0; Q2 = 0;
for (i = 0; i < N; i++){
Q0 = coeff * Q1 - Q2 + (float) Data[i];
Q2 = Q1; Q1 = Q0;
}
mag2 = (Q1*Q1) + (Q2*Q2) - Q1*Q2*coeff;
printf("%f\n", sqrt(mag2));
}
}
cwdecoder.cから核心部分を抜き出してきました。これ、中心周波数600Hz,帯域幅100Hzのバンドパスフィルターとして機能するのね。FFTみたいに、データ個数を2のべき乗にする必要なしってのが、便利でよさそう。
$ cat pg.out #! /usr/local/bin/gnuplot #set terminal png set nokey set grid #set logscale y plot '< cat -' with line pause 10
こちらは、gnuplot script です。pauseで10秒待つようにしました。こうしておかないと、 表示してすぐにグラフが消えてしまうからです。10の代りに -1 とすると改行入力がされるまで、停止するはずなんですが、機能しませんでした。まあ、スクリプトにチョッカイができない のは、あたり前の事だわな。
下記のようにパイプの終端で使っているので、セッションリーダーはcatが担当。catが終了すると、パイプの後段も全て死ぬので、悠長にキー入力なんて待っていられない? そう思って、単独で動かしてみたけど、やっぱり無視されてましたよ。(-pなんてオプションも機能しなかった)
$ cat sweep.raw | ./dut | ./pg.out
dutてのは、上のプログラムね。信号源から被試験デバイスのフィルターに信号を入力。その応答を可視化するために、オシロスコープを使う。これってネットワークアナライザーの原型ですよ。
現役時代は、そのオシロにポラロイドカメラを取り付けて、写真にとる。オシロの輝度やら露光を間違えると、白けた写真になっちゃう。今なら、pg.outスクリプトのset terminal pngを有効にするだけで、綺麗なpngが得られる。( .. | ./pg.out > picture.png 等とする)
signal generator
soxに作らせた掃引音源が、どうも周波数をリニアに発生していないようだ。低い周波数の時は長時間、高い周波数になるにつれて、短時間になってるっぽい。
予想するに、発生波形の個数を一定になるようにしてないかな。そうすると、(ある)周波数の持続時間は、周波数に反比例するな。正確に調べるならsoxのソース嫁。
面倒なんで、信号発生器を自作しちゃえ。
ob$ cat sg.c
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#define SAMPLE_FREQ (10 * 1000)
#define LEN 100
void gen(int freq){
double out;
int t;
short int b16;
for (t=0; t<LEN; t++){
out = sin( 2 * M_PI * freq * t / SAMPLE_FREQ );
b16 = (short int) (out * 3276);
fwrite(&b16, sizeof(short int), 1, stdout);
}
}
int main( int argc, char *argv[] ){
int f;
//a = atof(argv[1]);
for (f=100; f <= 3000; f += 10){
gen(f);
}
}
arduinoのA/Dの変換スピードに敬意を表して、サンプリング周波数は、10KHzにした。なんてのは、表向きの理由で、単に暗算が楽だからです。
サンプリングの個数と言うか長さは100個。時間にすると、10ミリ秒だけの期間で各周波数を発生。
発生させる周波数は、100Hzから3000Hzまで、10Hzステップという、プリセット版のスィープジェネレーターです。信号レベルは、ほぼ -10dB です。( 3276 / 32768 )
DUTに直結できる様に、信号出力は、バイナリーなrow形式にしています。
goertzel の Q
ob$ ./sg | ./dut | ./pg.out > scan.png ob$ gpicview scan.png
ピークが500Hzにあって、あれって思わないでください。X軸の最左が100Hzになってますんで、あしからず。
このフィルターのQはどれぐらいになるんですかね? センター周波数の信号レベルから3dB落ちた所の帯域幅との比って定義でしたっけ?
Freq BW=100 BW=50 BW=25
------ -------- -------- --------
41 0 0 0
42 1769 1639 1203
43 3667 2724 878
44 5536 3047 1041
45 7345 2339 1794
46 9151 0 0
47 10967 3585 2783
48 12668 7460 2336
49 14021 11162 3546
50 14823 14025 11257
51 14999 14999 14999 <== Center
52 14633 13956 11403
53 13880 11453 3512
54 12853 7752 2301
55 11550 3520 2929
56 9910 0 0
57 7930 2264 1940
58 5732 3338 1009
59 3540 3017 840
60 1585 1576 1350
61 0 0 0
BWを変えて(N=100,200,400)中心周波数+-100Hzの範囲(10Hz step)のレスポンスを表にしてみた。
サンプル数を変えると、値も変わるので、Nで除して正規化してある。また、入力振幅によって値が変わるのは常識なんで(線形応答)、見易い数値になるように、sgの振幅係数を、30000に設定しておいた。
sox synth
冒頭の所であげたsoxのsynth機能での掃引周波数発生。低い周波数はゆったりと、高い周波数はせわしなくってのが、どんな具合になってるか確認。どうやって?
無線家の常識、F = 1 / T という定義を思いだす。周期Tの逆数が、周波数Fだよってあれね。 周期なら、ちょっとの工夫で測定できるだろう。
#include <stdio.h>
#define SAMPLE_FREQ (10 * 1000)
#define N (SAMPLE_FREQ / 10)
short int Data[N];
int m2p(int offset){
int t;
for(t = offset; t<N; t++)
if (Data[t] < 0) break;
for(t; t<N; t++)
if (Data[t] > 0) break;
return (t);
}
void decide(){
int t,t0,t1;
t0 = m2p(0);
t1 = m2p(t0 + 1);
t = t1 - t0;
if (t)
printf("%d\n", SAMPLE_FREQ / t);
else
printf("0\n");
}
int main (){
int rc;
for (;;){
rc = fread(&Data, sizeof(short int), N, stdin);
if (rc < 1)
break;
decide();
}
}
0.1秒ごとにデータの塊を切り出す。その塊(配列)の中をスキャンして、マイナスからプラスに転じるポイントを2点みつける。その差が、周期になる。ポイント間は、1/SAMPLE_FREQという時間になってるからね。
すなわち、0.1秒間隔で、その時の周波数を求めているんだ。X軸は、発生開始からの経過時間(相当)、Y軸は周波数だ。Y軸を対数表記にしたら、直線になったよ。すなわち、経過時間に対して、周波数を対数的に発生させてるんですな。
音を扱う世界の対面には、人間がいる。物理的な刺激量と知覚量は、対数関係にあるっていう、ウェーバーヘィヒナの法則ってのが良く知られている。周波数検知も同様なの?
世の中には、絶対音感と言う特技の持ち主がいるそうだ。そういう人って、人間の格好をした周波数カウンターと思うんだけど、どうよ。
sin wave gen in golang
package main
import (
"encoding/binary"
"math"
"os"
)
const PI = 3.1415926
func main() {
var (
data float64
frequency float64 = 440 * math.Pow(2, 3/12) // 523 Hz
rate float64 = 44100
duration float64 = 3 // 3 secs
)
for n := 0; n < int(rate*duration); n++ {
// Calculate amplitude at the n / rate phase.
radius := frequency * 2 * PI * float64(n) / rate
data = math.Sin(radius)
// Render audio data to STDOUT.
binary.Write(os.Stdout, binary.LittleEndian, data)
}
return
}
go run main.go | play -r 44100 -c 1 -t f64 -