モールス信号decoder(まとめ)
前回、ちらりと書いた、アメリカ製トランシーバー、性懲りもなく、迂回輸入したら 幾らになるかと、識者に聞いてみました。気になる点は、現地の消費税と関税です。
税については、CAの消費税は各市で若干異なります。SFOですと9.5%です。 関税については、以前の経験では5%でした。 課税は、(商品価格*0.6+送料)*0.05と記憶しています。
運び屋さんへの謝礼も含めるとして、大雑把に考えると、商品価格 X 1.15 ぐらいで 考えておけばいいんだな。
10W完成品が、$1600。ATU $330, USB I/F $40, Crystal Filters $125 each っつう事で ざっくり、$2100 かあ。
後は、運び屋をどうするかだな。裏社会知らないから、これが一番の難関そう。 壊れると、米国へ送って修理になる。このターンアラウンドが、1.5月ぐらいだ そうだから、ちと辛い。
decoderの最終試験
オプションのスピード追従も出来たので、これを組み込んで、最終試験をやってみる。 信号発生器は、A1A Breaker 。 被試験品は、FreeBSD on VMWARE だ。NotePCの内部 スピーカーで音を聞くと、キークリックっぽく聞こえるので、ヘッドフォンとマイク を、手ぬぐいで包んで、音響結合した。
まず、コールサイン集があったので、これを試験信号とした。cwdを立ち上げ、受信 状態にしておき、信号発生、1フレーズが終わると、信号発生器のスピードを変え (追従性能を見る為)て、連続受信した。
DG8KNF XK0DGE NQ2AJJ LO5DVS AL7FH PV0GHF DO2OGJ AL0JBM/HR0 <-- ref DGLT TMNF XK0DGE NQ2AJJ LO5DVS AL7FH PV0GHF DO2OGJ AL0JBM/HR0 <-- 60chr/min SR6DIF XK0DGE N<??>2AJ<??> LW5DVS AL7FH PV0GHF DO2OGJ AL0<??>BM<??>HR0 <- 120 DG6<??>F XKEE<??>2A<??>L<HH>DVS AL7FH PV0GHF DO2G<??>I<??><??>HR0 <- 130 DG8KNF XK0DGE NQ2AJJ LO5DVS AL7FH PV0GHF DO2OGJ AL0JBM/HR0 <- 90
うーん、スピードが速くなると、付いていけないなあ。それじゃ、平文はどうかな?
Alice was beginning to get very tired of sitting by her sister on the <- ref ALICE A T TT D BEGINNING TO GET VERY TIRED OF SITTING BY HER SISTER <- 60 <??>ICE WAS BEGINNING TO GET VERY TIRED OF SITTING BY HER SISTER ON <- 120 ALICE WAS BEGINNING TO GET VERY TIRED OF SITTING BY HER SISTER ON THE <- 130 A L I CE EMAS BEGINNING TO GET VERY TIRED OF SITTING BY HER SISTER ON <- 90
まあまあか。
次は、平文で限界スピード(と、思われる 150文字/分)を、調べてみた。
ALICE WAS BEGINNING TO GET VERY TIRED OF SITTING BY HER SISTER ON THE BANK, AND OF HAVING NOTHING TO DO: ONCE OR TWICE SHE HAD PEEPED INTO THE BOOK HER SISTER WAS READING, BUT IT HAD NO PICTURES OR CONVERSATIONS IN IT, AND WHAT IS THE USE OF A BOOK,'
解読してる時、どのぐらいのロードアベレージになるか、確認しておきたかったんだ けど、on VMWAEW上 でのロードアベレージは信用ならんのでやめた。このあたりは リグによって良く振れる(振れない)Sメーターみたいなもんで、耳Sの方が信用なるか。
さすがに、裏JOBでコンパイル等を始めちゃうと、解析をミスるようになる。nice値を 上げると良くなるのかな。まあ、この辺は、シングルスレッドの限界だろう。
プログラム修正点
上記の実験で使った、ソース一式を巻末に付けておきます。 最終試験を行うに当たり、ちと修正した部分があるので、メモしておきます。
自動追従させる為の初期値を当初は、{6, ... 18, ...} としてたけど、{ 12 } に改めた。この方が、初回起動時の追従性が良かった。(@ tr.c)
自動追従中に符号データ数が10を超える場合があり、本体が強制終了しちゃう 事が有った。超えた場合は、符号データをリセットするようにした。(@ cwd.c)
早過ぎるオプティマイズは悪と言う掟が有ったので、今まで気にはなっていたけど 何もしてなかった。今回でこのシリーズも終わりにしようと思っているので、プチ オプティマイズをやってみた。FFTの返り値を、全周波数領域に渡って計算して たり、使いもしない周波数ドメインの値を配列に入れていた。 これは、今回に限っては無駄なので、必要なもののみにした。(それに伴い、 fftの引数が変わった)
Makefileを整理して、ちとスマートにした。これって DRY の実践になるのかな。
Linuxへの移植について
Linuxはメインで使っていないので、音関係はどうなっているか良く知りません。 伝え聞く所によると、音を扱う流儀は2種類あるとの事。
一つは、ALSA系。名前の一部にLinuxと入っているように、Linux専用の方言だ。 こちらは、意識してドライバーを追加しないといけないらしい。
もう一つの系統はOSS。Linuxを入れると、つられて入ってくるのかな。良く知らない や。
で、FreeBSDで使った、/dev/dspW は、OSS系だとか。よって、Linuxで使うなら OSS系の方が親和性が高いと思われます。(注: AlSAでもOSS系のふりをする事が 出来るとか。)
論より証拠とばかり、Debian/lenny へ持っていって、そのままコンパイル・起動 してみたら、エラーにもならずにモールス信号待ちになったよ。(残念ながら マイクジャックの口径が合わず、信号は入れられなかった。)
細かい事は分からないけど、openした時の、デバイスのデフォルト設定さえ確認 して、8000Hz,16Bit,mono ぐらいに設定出来れば、動くんではなかろうか。 (後で調べた限りでは、/dev/dspWの初期値は、FreeBSDのそれと一緒だったよ)
ちなみに、使ったlennyはこんなのでした。
sakae@debian:~/CW$ cat /dev/sndstat Sound Driver:3.8.1a-980706 (ALSA v1.0.16 emulation code) Kernel: Linux debian 2.6.26-2-686 #1 SMP Fri Aug 14 01:27:18 UTC 2009 i686 Config options: 0 Installed drivers: Type 10: ALSA emulation Card config: ESS ES1938 (Solo-1) rev 0, irq 5 Audio devices: 0: ESS Solo-1 (DUPLEX) Synth devices: NOT ENABLED IN CONFIG Midi devices: 0: ESS ES1938 (Solo-1) MIDI Timers: 7: system timer Mixers: 0: ESS Solo-1
ソース開陳
Makefile
# Makefile for CW decoder OBJS = cwd.o lookup.o tr.o a.out: $(OBJS) cc -lm -o $@ $(OBJS) .c.o: cc -g -c $< #cwd.o: cwd.h
tr.c
// auto speed tracking
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#define KEEP 16 // mcnt keep count
int rp = KEEP; // ring pointer for kp
int kp[KEEP] = { 12 }; // history (init value)
int int_cmp(const int *a, const int *b){
if (*a == *b){ return 0; }
if (*a < *b){ return -1; } else { return 1; }
}
int tr(int v){
int i, work[KEEP];
int dot = 0; // sum of dot
int dash = 0; // sum of dash
if (rp == KEEP){ rp = 0; }
kp[rp++] = v; // Update history
bcopy(&kp, &work, sizeof(int) * KEEP); // copy to work
qsort(work, KEEP, sizeof(int), (int(*)(const void*, const void*))int_cmp);
for(i = 0; i < KEEP; i++){
// printf(" %d", work[i] ); // debug
if ( i < KEEP/2 ) { dot += work[i]; } else { dash += work[i]; }
}
// printf(" -> %d %d ", dot / (KEEP/2), dash / (KEEP/2)); // debug
// if (dash > dot * 2){ printf("OK\n"); } else { printf("ng\n"); } // debug
if (dash > dot * 2){ return dot / (KEEP/2); } else { return 0; }
}
/******** for debug ***********************
main(){
int dot_dash; // simulate mcnt
while(1){
printf("Next dot/dash ==> ");
scanf("%i", &dot_dash);
tr(dot_dash);
}
}
*******************************************/
lookup.c
// lookup morse code to char
#include <stdio.h>
struct kv {
long key;
char val[8];
};
struct kv tbl[] = {
{ 13, "A" }, { 3111, "B" }, { 3131, "C" },
{ 311, "D" }, { 1, "E" }, { 1131, "F" },
{ 331, "G" }, { 1111, "H" }, { 11, "I" },
{ 1333, "J" }, { 313, "K" }, { 1311, "L" },
{ 33, "M" }, { 31, "N" }, { 333, "O" },
{ 1331, "P" }, { 3313, "Q" }, { 131, "R" },
{ 111, "S" }, { 3, "T" }, { 113, "U" },
{ 1113, "V" }, { 133, "W" }, { 3113, "X" },
{ 3133, "Y" }, { 3311, "Z" },
{ 33333, "0" }, { 13333, "1" }, { 11333, "2" },
{ 11133, "3" }, { 11113, "4" }, { 11111, "5" },
{ 31111, "6" }, { 33111, "7" }, { 33311, "8" },
{ 33331, "9" },
{ 131313, "." }, { 331133, "," }, { 113311, "\?"},
{ 31113, "=" }, { 311113, "-" }, { 333111, ":" },
{ 133331, "'" }, { 31131, "/" }, { 133131, "@" },
{11111111, "<HH>"}, { 13131, "<AR>" },
{ 0, "<\?\?>" } // case not found. (don't delete this line)
};
char* lookup(long k){
int i = 0;
while(1){
if (tbl[i].key == 0){ return tbl[i].val; }
if (tbl[i].key == k){ return tbl[i].val; }
i++;
}
}
/******* for test **************************
main(){
printf("%s\n", lookup(atol("3111")));
printf("%s\n", lookup(atol("33331113")));
}
********************************************/
cwd.c
// Morse code decoder
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <unistd.h>
#define SOURCE "/dev/dspW" // Morse tone from audio device
//#define SOURCE "sound.au" // Morse tone from raw file
#define DATAN 128 // FFT point #
#define MAXELE 10 // max dot dash element (par char)
#define DUMP(fmt,val) do{ if (argc == 2 ){ printf(fmt, val); } } while(0)
double Wave[DATAN]; // wave buffer
double fft(double *tW, int fp) {
int N = DATAN;
int N_2 = N/2;
int i,j,k,kp,m,h,P;
double mdd;
double w1, w2;
double t1,t2,s1,s2;
double tri[DATAN];
double fReal[DATAN], fImag[DATAN];
// Get P, where N = 2 ** P
i = N; P = 0;
while (i != 1) {
i = i / 2;
P++;
}
// copy real wave into buffer
for (i = 0; i < N; i++) {
fReal[i] = tW[i]; fImag[i] = 0.0;
}
// make sin,cos table
for ( i = 0; i < N_2; i++ ) {
tri[i] = cos( 2 * i * M_PI / N );
tri[i + N_2] = (-1.0) * sin( 2 * i * M_PI / N );
}
// sort
j = 0;
for ( i = 0; i <= N - 2; i++ ) {
if (i < j) {
t1 = fReal[j]; fReal[j] = fReal[i]; fReal[i] = t1;
t2 = fImag[j]; fImag[j] = fImag[i]; fImag[i] = t2;
}
k = N_2;
while (k <= j) {
j = j - k; k = k/2;
}
j = j + k;
}
// do butterfly
for ( i = 1; i <= P; i++ ) {
mdd = pow(2.0, (double)i);
m = (int)mdd;
h = m/2;
for ( j = 0; j < h; j++ ) {
w1 = tri[j*(N/m)];
w2 = tri[j*(N/m) + N_2];
for( k = j; k < N; k+=m ) {
kp = k + h;
s1 = fReal[kp] * w1 - fImag[kp] * w2;
s2 = fReal[kp] * w2 + fImag[kp] * w1;
t1 = fReal[k] + s1; fReal[kp] = fReal[k] - s1; fReal[k] = t1;
t2 = fImag[k] + s2; fImag[kp] = fImag[k] - s2; fImag[k] = t2;
}
}
}
return sqrt(fReal[fp] * fReal[fp] + fImag[fp] * fImag[fp])/DATAN;
}
load_wave(double *tW, int fd){
int i;
signed short buf[DATAN];
read(fd, &buf, sizeof(signed short) * DATAN);
for (i = 0; i < DATAN; i++){
tW[i] = (double)buf[i];
}
}
main(int argc, char* argv[]){
int count = 491520 / DATAN; // samples @ 8000/sec * 60sec
int fd;
double th = 800.0; // thereshold for 1/0
int dot = 6; // dot length
int onoff; // Signal on(1) or off(0)
int ms = 0; // mark(1) or space(0)
int mcnt = 0; // mark counter
int scnt = 0; // space counter
char val[MAXELE]; // dot(1),dash(3)
int x = 0; // index for val
int ws= 1; // word-space print enable
int yes; // for auto speed tracking
fd = open(SOURCE, O_RDONLY);
while(1){
load_wave(Wave, fd);
onoff = fft(Wave, 10) > th ? 1 : 0; // 10 is 625Hz amplitude
DUMP("%d",onoff);
if (onoff != ms ){ // mark->space or space->mark
ms = onoff;
if (ms) { mcnt = scnt = 0; ws = 1; } else { scnt = 0; }
}
if (ms) {
mcnt++;
} else {
scnt++;
if ( mcnt && (scnt > 1) ){ // in char
val[x++] = mcnt > dot * 2 ? '3' : '1';
yes = tr(mcnt);
if (yes) { dot = yes; } // auto speed tracking
mcnt = 0;
if (x == MAXELE){ x = 0; } // fatal, reset char
}
if ( x && (scnt > dot * 2)){ // detect char
val[x] = '\0';
printf("%s", lookup(atol(val)));
fflush(stdout);
DUMP("%s", "\n");
x = 0;
}
if ( ws && (scnt > dot * 5) ){ // detect word
printf("%s", " "); ws = 0;
DUMP("%s", "_\n");
}
}
}
close(fd);
printf("%s", "\n");
exit(0);
}