cw decoder
正月に兄貴と飲んだ時、また東京へ行きたいから案内せいと依頼された。オイラーは、東京怖い、他の所なら付き合うよと返答。かくして、加賀百万石へと行ってまいりました。オイラーはついていくだけのラクチン旅であります。
兼六園は、65歳以上って証明出来れば、無料入園。その他の施設も年寄り割引が有ったりするんで、堂々と利用しましょう。
散歩で鍛えた足で街をブラブラしてたら兄貴は少々グロッキーぎみ。たまたま近くにあった 金沢蓄音器館に休憩方々、入館した。
蓄音機10台の聞き比べに時間がシンクロしたんで、拝聴してみた。どうせ蚊の鳴くようなほそぼそとした音だろうと思っていたんだけど大外れ。大音響にびっくりしました。 裏側から、電線が伸びていてアンプしてるんじゃないかと、マジで確認しちゃったぞ。
音量の調整はどうやるかって説明があった。朝顔型のホーンを外す、前蓋を閉めると言う、超アナログ方式には笑ってしまったぞ。
ビクターの有名なマーク、犬が蓄音機に耳を傾けているのの説明があった。 ニッパー (犬)が、亡きご主人様の声を聴いている訳ね。なんだか、渋谷にいる忠犬ハチ公のイギリス版みたいだな。
大音量のエネルギーは何処から得ているの? 学芸員に質問を投げるもわからんそうだ。 動作原理/サウンドボックス とかでも、エネルギーについては言及されていないなあ。
ああ、前田の殿様はお役御免になって暇を持て余し、オーディオマニアになったとか。球でアンプを作ったりしてたそうだ。織田信長みたいに新しい物が好きだったのね。
夜は言わずと知れた宴会。中々食べる機会が無い、ホタルイカの天麩羅なんてのと、 ふぐの子糠漬けを堪能したぞ。
兄貴は、そんな河豚の卵巣なんて、、、まだ死にたくないと抜かす。で、冥土の土産に食べてみろって勧めたら、恐る恐る一切れ食べた。そして箸が止まらなくなった。
cw decoder
ハムな友人から、面白いやつを見つけたんだけどと紹介された。
EASY BUILD CW DECODER BASED ON DSP GOERTZEL CODE
オイラーが昔やったのと少し似ているな。fftじゃなくてgoertzelとか言うアルゴリズムを使って、信号を検出してる。計算量が少いのでパワーのない石でも使用可能とか。
特許になってるね。
arduino
実装してるのはPIC似な石だ。
CWのオーディオ信号をそのまま入力してるんで、内部にA/Dを搭載してるんだろうね。
調べてみたら、確かにのってた。スペックは、0-5Vを1024に分解。サンプリングスピードは、100マイクロ秒/回との事。なんだか、H8と同様だな。
source
ソースは下記から手にはいる。サフィックスに、.inoって付いているけど、普通のC言語だから、恐れる事はない。
Arduino Based CW decoder. *UPDATED* source-code
実行戦略(遊び方)
試してみたいんだけど、ボードを買わないと無理? 知恵を出せ。パソコンでシュミレートしちゃえ。
CWの音の代りに音ファイルから入力させよう。その為には、少々のソース変更は覚悟するよ。まあ、汗をかけって事だな。
その音ファイルはどうやって作りますかね。そんなの前にやっただろう。キッチンタイマーで時間がきたら、ブザーじゃなくてモールスでおしえてくれるやつを。
driver
そうモールス音ファイル作成アプリケーション
こやつは、WAV形式出てくる。形式をおさらいしとく。
どうやら頭に余計なヘッダーがついているんで、正式にはそれを落すソフトが必要。
一応、遊び人のためのLinuxであるdebinでやってみる。(うぶでもいいけど)
debian:WAV$ cwwav -h Usage: cwwav OPTIONS [FILENAME...] Convert text into an audio file with morse code. Mandatory arguments to long options are mandatory for short options too. -s, --stereo generate stereo output (by default 2 identical channels) -p, --phaseshift N shift phase of right channel by N radians (stereo effect) -o, --output specify output file (must be supplied for WAV output) -O, --output-format specify output file format (wav, default: wav) -f, --frequency=N use sidetone frequency N Hz (default: 660) -r, --rate=N sample rate N (default 16000) -w, --wpm=N use N words per minute (default: 25) -F, --farnsworth=N use N WPM Farnsworth speed (default: same as WPM) -e, --envelope=N envelope N ms raised cosine (default=10.0) -h, --help display this help and exit
こんなコマンドで音ファイルを作成。それを、sox付属のplayコマンドで鳴らしてみる。
debian:WAV$ echo BT 25 hello cw world AR | cwwav -r 8000 -o 25.wav Processing standard input debian:WAV$ play 25.wav 25.wav: File Size: 183k Bit Rate: 128k Encoding: Signed PCM Channels: 1 @ 16-bit Samplerate: 8000Hz Replaygain: off Duration: 00:00:11.42 In:100% 00:00:11.42 [00:00:00.00] Out:91.4k [ | ] Hd:0.0 Clip:0 Done debian:WAV$ sox 25.wav 25.raw
ファイルの情報も確認できるのね。
sakae@usvr:~/WAV$ sox 25.wav -n stat Samples read: 91392 Length (seconds): 11.424000 Scaled by: 2147483647.0 Maximum amplitude: 0.999969 Minimum amplitude: -0.999969 Midline amplitude: 0.000000 Mean norm: 0.273823 Mean amplitude: -0.000035 RMS amplitude: 0.459803 Maximum delta: 0.504852 Minimum delta: 0.000000 Mean delta: 0.138431 RMS delta: 0.232232 Rough frequency: 643 Volume adjustment: 1.000 Can't guess the type
後で試験用の音ファイルを作成するんで、スクリプトにしておく。
JH1LHVの雑記帳で公開されてる練習機は、初期のトーンが750Hzになってる。歳を取ったせいか、高めなのは駄目だなあ。バイナリアンを自認するオイラーとしては、512Hzしかありません。ついでにサンプリング周波数も、それなりに変更しておいた。
#! /bin/sh # Usage: ./gen.sh WPM word ... spd=$1 shift head="BT "$spd tra="AR" echo $head "$@" $tra | cwwav -r 8192 -f 512 -w $spd -o ${spd}.wav sox ${spd}.wav ${spd}.raw
15 WPMで、CQ DE JA1RL って叩いてみる。(会員じゃないのにスマソ)
debian:WAV$ ./gen.sh 15 cq de ja1rl Processing standard input debian:WAV$ play 15.wav :
BT 15 CQ DE JA1RL AR と聞こえてくるはずだ。先週は東京オリンピックのチケット申し込みで、Webサイトに行列ができる騒ぎがあったみたいだけど、オイラーは、next tokyo って事で、parisを用意したよ。(いえね、wpmが大手を振ってまかり通っているけど、paris速度が好きなんですよ、と本心を露呈しました)
ここに、A1Aな波形(paris.pdf)やら、各種音源、改造したソースを置いておいた。
敵を知る
のが、戦に勝つ常道。って事で、冒頭の作者のページを眺めている。
搭載してる機能は、Lチカによる、目でCW信号を確認。LCDパネルにデコードした文字を垂れ流しするメイン部分。ソースをざっと見した所、シリアルポートにも、ドット、ダッシュで信号が出てくるみたい。
-.-./--.-/-.-./--.- -../. .---/.-/.----/.-./.-..
こんな具合に、ワード認識で改行しながらの表示をするはず。
敵と言えばarduinoもそうだな。setup()はプログラム起動時に一度だけ呼び出される。それはいいんだけど、loop()の中を永久に回り続けるって、以前にやったp5.jsのdraw()みたいなもの? あれは、1秒間に、50回だかって回数が決ってた。arduinoのそれは、全速力でグルグルするのかな。
音データを取り込む
てはじめに、簡単なやつをば、
cat 20.raw | cwdecode cwdecode < 20.raw
ってな具合にデータを渡すようにしよう。
// test basic loop #include <stdio.h> #include <sys/time.h> #define N 128 short int testData[N]; int usecs (){ struct timeval t; gettimeofday(&t, NULL); return ((t.tv_sec % 100000) * 1000000 + t.tv_usec); } void dump(){ for (int i=0; i<N; i++){ printf("%d\n", testData[i]); } } int main (){ int cnt=0; int sum=0; int t; int rw; for (;;){ t=usecs(); rw = fread(&testData, sizeof(short int), N, stdin); if (rw < 1 ) break; sum += (usecs() - t); cnt++; // dump(); } printf("cnt= %d sum= %d\n", cnt, sum); }
usecs()ってのは、マイクロ秒でカウントするやつね。これを使って、データの取り込みにかかった時間をだしてみた。
debian:WAV$ cc test.c debian:WAV$ cat 20.raw | ./a.out cnt= 769 sum= 1579
128個づつ読みこむと、769回で終了。1回の読みこみに、約2usの時間を要したって事がわかる。
また、dump()を有効にすると、グラフソフト(gnuplot)に渡すデータが得られる。 サンプルにはいってるparis.pdfはこれを使って作った。
0 12539 23169 30272 32767 30272 23169 12539 0 -12539 -23169 -30272 -32767 -30272 -23169 -12539 0
これを見て、サイン波って分かる人は、特異な才能の持ち主ですよ。
test run
サンプル音源とソースファイル2本を入手します。 cwdecodeサンプル そして、以下のように準備します。
$ unzip raw-smpl.zip Archive: raw-smpl.zip creating: raw-smpl/ extracting: raw-smpl/35.raw extracting: raw-smpl/30.raw extracting: raw-smpl/25.raw extracting: raw-smpl/20.raw extracting: raw-smpl/15.raw extracting: raw-smpl/10.raw $ mv cwdecode.c test.c raw-smpl $ cd raw-smpl $ ls 10.raw 20.raw 30.raw cwdecode.c* 15.raw 25.raw 35.raw test.c*
まずは簡単に実行。(毎度Linuxでは飽きるので、64Bit版のFreeBSD上です)
$ cc test.c $ ./a.out < 35.raw cnt= 1524 sum= 1199 $ ./a.out < 10.raw cnt= 5443 sum= 3735
そして、本命の改造版です。コンパイル時に、数学用のLibのリンクを忘れないようにね。
$ cc cwdecode.c -lm $ ./a.out < 20.raw BT SE QUICK BROWN FOX JUMPS OVER THE LAZY DOG. 1234567890 AR
なんか、ちょこっとミスしてるなあ。
今度は、20WPMと25WPMの音を連続して解読させてみます。
$ cat 20.raw 25.raw | ./a.out BT 20 THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG. 1234567890 AR BT 25 THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG. 1234567890 AR
これがLCDパネルに表示されるのね。改行と言う概念がないから、こうなるんだな。
今度は、何度か実行。出だしでミスるのは、ウォーミングアップされてないせい?
$ cat 20.raw | ./a.out 6SE QUICK BROWN FOX JUMPS OVER THE LAZY DOG. 1234567890 AR $ cat 20.raw | ./a.out BT 20 THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG. 1234567890 AR $ ./a.out < 20.raw BT 20 THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG. 1234567890 AR
改造した点
Lチカ、LCD、Serialのドライバー部分は大胆に削除。
setup(),loop()を呼び出すmain()を追加。
int main(){ int rw; setup(); for (;;){ rw = fread(&testData, 2, (int)n, stdin); if (rw < 1) break; usleep(100 * (int)n); loop(); } printf("\n"); exit(0); }
loop()の中に有った、A/Dからのデータ読み出しは、main()内に移してfreadで代用。いきなりfreadしちゃっていいのかと言うのは無しね。stdinはアプリの起動時には用意されてますから、これで良いのです。 freadで実際に読んだ個数がrwに得られるので、これを頼りにEOFを検出。
usleep()は、マイクロ秒単位の遅延をする関数。この関数を使って、実ボードのA/Dの遅さを実現してる。 最初これを省いていて、一瞬で終了してしまい、結果がおかしくて悩んだ。似せるの重要。
arduino特有の関数として、millis()が使われている。機能を調べてみると、通電後からの経過時間をミリ秒単位で返すとあった。約50日でオーバーフローする仕様なんで注意してねって説明されてた。その通りに実装したよ。
/// millis counter, over flow about 50 day's int millis (){ struct timeval t; gettimeofday(&t, NULL); return ((t.tv_sec % 4320000) * 1000 + t.tv_usec / 1000); }
オーソドックスに、gettimeofday()関数を使って実現。1970/01/01からの経過秒と秒の下位は、マイクロ秒になっているので、それを加工したよ。
docode()関数の中で、該当するトン/ツー記号から、文字を割り出している。そして、それをLCDに送るんだけど、ちょっと改変した。
void toOut(int asciinumber){ putchar(asciinumber); fflush(stdout); }
一文字づつ出力すると、改行があるまで表示されない(バッファリングされる)。この挙動から逃れる方法は色々あるけど、安直にfflush()で、バッファーから吐き出すようにした。
本体の論理は、極力手をつけていない。どんな流れか追ってみるかな。
man ascii
文字コードが整数で記述されたので、下記の右側の表を参考に。こんなのを載せると、モールス初心者って事がバレバレだな。
Tables For convenience, let us give more compact tables in hex and decimal. 2 3 4 5 6 7 30 40 50 60 70 80 90 100 110 120 ------------- --------------------------------- 0: 0 @ P ` p 0: ( 2 < F P Z d n x 1: ! 1 A Q a q 1: ) 3 = G Q [ e o y 2: " 2 B R b r 2: * 4 > H R \ f p z 3: # 3 C S c s 3: ! + 5 ? I S ] g q { 4: $ 4 D T d t 4: " , 6 @ J T ^ h r | 5: % 5 E U e u 5: # - 7 A K U _ i s } 6: & 6 F V f v 6: $ . 8 B L V ` j t ~ 7: ' 7 G W g w 7: % / 9 C M W a k u DEL 8: ( 8 H X h x 8: & 0 : D N X b l v 9: ) 9 I Y i y 9: ' 1 ; E O Y c m w A: * : J Z j z B: + ; K [ k { C: , < L \ l | D: - = M ] m } E: . > N ^ n ~ F: / ? O _ o DEL