cw decoder

正月に兄貴と飲んだ時、また東京へ行きたいから案内せいと依頼された。オイラーは、東京怖い、他の所なら付き合うよと返答。かくして、加賀百万石へと行ってまいりました。オイラーはついていくだけのラクチン旅であります。

兼六園は、65歳以上って証明出来れば、無料入園。その他の施設も年寄り割引が有ったりするんで、堂々と利用しましょう。

散歩で鍛えた足で街をブラブラしてたら兄貴は少々グロッキーぎみ。たまたま近くにあった 金沢蓄音器館に休憩方々、入館した。

蓄音機10台の聞き比べに時間がシンクロしたんで、拝聴してみた。どうせ蚊の鳴くようなほそぼそとした音だろうと思っていたんだけど大外れ。大音響にびっくりしました。 裏側から、電線が伸びていてアンプしてるんじゃないかと、マジで確認しちゃったぞ。

音量の調整はどうやるかって説明があった。朝顔型のホーンを外す、前蓋を閉めると言う、超アナログ方式には笑ってしまったぞ。

ビクターの有名なマーク、犬が蓄音機に耳を傾けているのの説明があった。 ニッパー (犬)が、亡きご主人様の声を聴いている訳ね。なんだか、渋谷にいる忠犬ハチ公のイギリス版みたいだな。

大音量のエネルギーは何処から得ているの? 学芸員に質問を投げるもわからんそうだ。 動作原理/サウンドボックス とかでも、エネルギーについては言及されていないなあ。

ああ、前田の殿様はお役御免になって暇を持て余し、オーディオマニアになったとか。球でアンプを作ったりしてたそうだ。織田信長みたいに新しい物が好きだったのね。

夜は言わずと知れた宴会。中々食べる機会が無い、ホタルイカの天麩羅なんてのと、 ふぐの子糠漬けを堪能したぞ。

兄貴は、そんな河豚の卵巣なんて、、、まだ死にたくないと抜かす。で、冥土の土産に食べてみろって勧めたら、恐る恐る一切れ食べた。そして箸が止まらなくなった。

cw decoder

ハムな友人から、面白いやつを見つけたんだけどと紹介された。

EASY BUILD CW DECODER BASED ON DSP GOERTZEL CODE

オイラーが昔やったのと少し似ているな。fftじゃなくてgoertzelとか言うアルゴリズムを使って、信号を検出してる。計算量が少いのでパワーのない石でも使用可能とか。

トーン信号回路およびそれを用いる無線受信機

特許になってるね。

GoertzelアルゴリズムでDTMF検出

MAXQ積和演算ユニット(MAC)を使った信号処理

arduino

実装してるのはPIC似な石だ。

Arduino

CWのオーディオ信号をそのまま入力してるんで、内部にA/Dを搭載してるんだろうね。

analogRead()

調べてみたら、確かにのってた。スペックは、0-5Vを1024に分解。サンプリングスピードは、100マイクロ秒/回との事。なんだか、H8と同様だな。

source

ソースは下記から手にはいる。サフィックスに、.inoって付いているけど、普通のC言語だから、恐れる事はない。

Arduino Based CW decoder. *UPDATED* source-code

with wabun

実行戦略(遊び方)

試してみたいんだけど、ボードを買わないと無理? 知恵を出せ。パソコンでシュミレートしちゃえ。

CWの音の代りに音ファイルから入力させよう。その為には、少々のソース変更は覚悟するよ。まあ、汗をかけって事だな。

その音ファイルはどうやって作りますかね。そんなの前にやっただろう。キッチンタイマーで時間がきたら、ブザーじゃなくてモールスでおしえてくれるやつを。

driver

そうモールス音ファイル作成アプリケーション

cwwav

こやつは、WAV形式出てくる。形式をおさらいしとく。

音ファイル(拡張子:WAVファイル)のデータ構造について

どうやら頭に余計なヘッダーがついているんで、正式にはそれを落すソフトが必要。

soxコマンド覚書

今更聞けない目的別soxの使い方

一応、遊び人のための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速度が好きなんですよ、と本心を露呈しました)

cwdecodeサンプル

ここに、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