モールス信号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);
}