Workbench(3)

防衛省で、球形飛行体 なんてのを研究してるらしい。市ヶ谷のあそこですかね。昔はおいらも あの辺へよく通ったものだ。 あの辺は、怪電波が出てるらしくて、一緒になった元東大の教授様が、電波腕時計があのあたりへ行くと 狂うとおっしゃっていたっけ。怪しさ満載の市ヶ谷・四ツ谷エリアですよ。

だけど、あんな飛行物体が秋葉原で材料を仕入れて作れちゃうって、怖いな。あそこに灯油と燐でも混ぜた たいまつを吊るして、お墓の回りを旋回させたら、涼しくなるぞーーー。防衛省さんよ、今年の夏は、節電 グッズとして、貸し出したらどうでっしゃろ?

ネーミングは、これで涼しさ満点、クールスポット ぐらいでいいかな。防衛省のお膝元には、お岩さん とか三島さんとかがおられるはずですから、これらの方とタイアップすれば、もう万全でっせ。

こちらは、噂によると例の爆発現場を撮影した飛行物体だそうです。カメラの代わりに、O-104でも搭載して、 ばら撒くとかは、考えないで下さい。

どうせなら、かわいいやつがいいです。上と同じ会社で作っているそうです。おいらも一羽欲しい。餌は 何でしょうね? 電気を食べているんでしょうか?小さくする事が得意な日本の技術を持ってすれば、蚊 ぐらいのサイズに出来るかな? 京で世界一になったけど、マイクロマシーンとかMEMS分野でも、小さいので 世界一を目指してくらはい。

AMPM

前回は、振幅変調器とかを作ってみた。まだまだ電子工作は続くよ。今度は周波数変調をやってみるか。 周波数変調って、キャリアーがふらふらするやつね。昔はVFOを自作したけど、なかなか安定しなくて 苦労した事を覚えている。VFOが温度計になっちゃりしてね。そう、温度で発振周波数が勝手にドリフト しちゃうんだ。

で、そのドリフトを、音声の大小によって能動的に変えてしまえば、周波数変調器になるんだ。 電子工作的には、次のようになるかな。

def fm(fs, fc=300, m=0.5):                    # freq. modulation
    for n in range(SMPL):
        fs[n] = cos(2 * pi * (fc + m * fs[n]) * n / SMPL)
    return fs

FMと言うとステレオを思い出す。おいらまっとうなクラッシックファンで、よくダビングしたっけ。ステレオ は、左右のスピーカーから別々な音が出てくるけど、どうなってるんだ? この際だからちょいと調べてみた。

FMステレオ方式に よると、AM-AM方式と和差方式とスイッチング方式があるんだな。面白い。

AM,FMと来たら、次はPMだな。PMが揃えば、AMPMでコンビニが一丁出来上がり。簡単だから、ここでは いちいち表示しないけど、アマチュアの世界でPMって使われているんかいな? こちら経由で なじみのあるのをを 見ると、確かに許されてますな。ファクシミリがそうか。最近は聞かないけど、昔はやってる人が居たような。。。。

CW送信機

各種の変調器が出来たんなら、A1Aの信号も発生したいよね。A1Aの送信機を作ってもいいんだけど、ちょいと 汎用的に考えてみる。パドルのon/offによって、搬送波が出たり出なかったりすればいいわけだ。

見方を変えると、常に発振してる搬送波とパドル信号のAND(論理積)を取ってあげれば良い。言い方を 変えると、搬送波をパドル信号でゲーティングするって事だ。そうすると、2入力のAND回路か。はて、 パイプの途中に信号を注入するんだな。

きっとA1Aクラブの人は、これを『ラブ注入』って言うんだろうな。

途中に注入するには、AND回路の一方の入力端子に、信号の発生手順を教えてあげれば良い。 勿論もう一方は、パイプの 上流から流れてくる信号ね。AND回路は、掛け算で実現出来るな。

mix  = lambda a,b: array((a + b))             # add array
mul  = lambda a,b: array((a * b))             # product array

ついでなので、足し算も定義しておいた。 一応、array間の演算動作を、Pythonのリストと比べてみる。まずはリストから

>>> a = [1,2,3,4]
>>> b = [10,11,12,14]
>>> a + b
[1, 2, 3, 4, 10, 11, 12, 14]
>>> a * b

Traceback (most recent call last):
  File "<pyshell#6>", line 1, in <module>
    a * b
TypeError: can't multiply sequence by non-int of type 'list'
>>> a + 3

Traceback (most recent call last):
  File "<pyshell#14>", line 1, in <module>
    a + 3
TypeError: can only concatenate list (not "int") to list
>>> a * 3
[1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4]

一杯、エラーを喰らっちゃったよ。まあ、一部にLispっぽいのが混じっているね。それに対して、リストをarrayに変換して おくと。。。

>>> x = array(a)
>>> y = array(b)
>>> x + y
array([11, 13, 15, 18])
>>> x * y
array([10, 22, 36, 56])
>>> x * 5
array([ 5, 10, 15, 20])
>>> x + 3
array([4, 5, 6, 7])

行列の出来る、numpyって事ですね。(事前に、numpyをimportしておく事)

wb> st(fs1=300) | mul(dd(m=1,s=0)) | dt

シングルトーン発振器から高い周波数(キャリアー)を得て、それとキーイング情報を掛け算する。 気分は、グリッドバイアス・キーイングだな。

1は何回掛け算しても、元の値と同じと言う、掛け算の 単位元です。ゼロを掛けると元がどんな値でもゼロになるって言う、数学では当たり前の事実を利用して います。単位元なんて言葉は、先の東大教授様が訳されたSICPを読んだ時に出てきました。それ以来、 単位元、それから式年遷宮とGCの繋がりを忘れる事はありませんでしたよ。

この20年毎に繰り返される、お伊勢様の神社移転は、技術の伝承を保障するために編み出されたしきたり と言うのが、もっぱらの通説になっているそうです。で、GCが何で関連してくるかと言うと、STOP and COPY によるGCなんですね。言われてみれば、その通り。例のGC本には書いてあるのかな? ああ、無駄話だったわい。

盗み見

パイプの途中に信号を注入する方法は分かった。そうなると、パイプの途中にどんな信号が流れて いるか、盗み見 したくなる。(今までの波形観測機、dt,df は、パイプの終端でしか機能しなかった)

unixのパイプの先生に聞いてみると、tee じゃよと。ついでに、パイプの先端にファイルからデータを 注入する機能や、データをファイルに落とす機能が無いぞと指摘されちゃったよ。嗚呼、やぶ蛇に なっちゃったよ。

そこで、先生には、ファイルに落とす演算子(unixのshellでは、有名な、> 記号ね)は、名前を 考えてあります。その名前は、セーブオンですって、コンビニ繋がりで咄嗟に思い出したのを 口走ってしまったよ。saveon('hoge.txt') なら、語感がいいからね。でも、もう一つ有名な、< に変わる やつは、いい名前が思い付かない。ここは、名前が決まらないと実装しないと言う、あの人に習って おこう。それより、盗み見の方が、需要だよ。

あの人に習って、まず名前を決めよう。teeでもいいけど、アマチュア(無線)的な名前の方が親しみが 有っていいだろう。機能的には、パイプに挿入して信号をモニターするんだから、方向性結合器、サーキュレーター になるんかな。

トランシーバーとかの基板を見ると、要所要所に信号モニター用のテストポイントが立っていたりする。いわゆる tpか。まあ、それでもいいんだけど、ちょっと悪ぶって、tapなんてのはどうだろう。

どうせtapするなら、信号は自動検出したいね。実数が流れていたら、ああ、タイムドメインで表示するんか。 複素数なら、周波数ドメインで表示だ。周波数ドメインだと、振幅特性と位相特性があるな。どちらを 表示させる。SWを付けて選ばせるか? 自動検出までやってくれるなら、両方同時に表示させちゃえ。 そうすれば、考える事いらんわな。

まあ、自動検出って考えた時点で、信号の推測を放棄しちゃってる訳 だから、そんなの今のお前には10年早いわと言われれば、返す言葉は有りませんが。。。デジタルオシロとかが 出てきて、信号を推測しながら観測するって風潮が失われたのは、今の電子業界に取って、痛し痒しです。 ついでに書いておくと、放射能を急に測り出して、大きい小さいって一喜一憂してるけど、3.11前の値を 各地で測ったデータって有るんでしょうかね?

def tap(a):                                   # tapping  
    figure()
    if True in iscomplex(a):
        #subplots_adjust(hspace=0.001) 
        subplot(211)
        f = geta(a)
        bar(arange(SMPL/8), f[:SMPL/8])        
        subplot(212)
        p = gett(nb(a))
        bar(arange(SMPL/8), p[:SMPL/8])
    else:
        plot(a)
    return array(a)

def disp(a):                                  # put on pipe end
    tap(a)
    show()

iscomplexで複素数を判定する時、最初は、a[0] を見てたんだ。3.12+0j みたいな虚部が ゼロの値が出てきて、Trueにならないのね。しょうが ないので、上記のようにしましたよ。でも今考えたら、a[0]って、直流成分の所じゃん。直流分に 虚部があったらまずいんで、積極的に0jにしてるんかな?

それから、今はコメントにしてある部分を生かすと、上側のグラフ (の表示部分)と下側のグラフのくっついてくれます。

自分用のメモとして、subplot(abc) の、aは、縦方向のグラフ数、bは横方向のグラフ数、cは描画対象の グラフ番号を表している。

dispは、パイプの終端に入れるターミネーターです。これを入れておかないと、SWRが無限大になってしまい、 保護機能が働いて、結果が表示されないままパイプが終了してしまいます。終端は必ずターミネート しましょう。

ろ過器

次は、福島方面で活躍してる、ろ過器を取り上げます。不要なのを除去して必要なものだけ取り出すアレです。 無線機の中には、水晶で出来たのがいいですぜと、メーカーからの甘い誘いにそそのかされて、高価なやつを いっぱい入れてる人もいるでしょう。高いのを買うと運が向上して、いろいろな賞状がホイホイと手に 入るんでしょうか。 だとしたら、一本30万円の開運印鑑と一緒かな? お年寄りは注意して、甘い言葉には騙されないように しましょう。

世の中には、いろいろなタイプのろ過器が出回っています。たとえば、 料理本に出てた、 スムースとか、 FIRさんカルマン方式、 等。ろ過すれば、もっと見つかるかも知れないよ。

で、おいらは、金が無いので、手を動かします。 まずは、ノイズ交じりの信号だな。ノイズをmixすればいいんだな。

wb> st | tap | mix(tt(fs1=39,fs2=51)) | tap | fft | disp

入力信号をモニターし、ノイズ相当(周波数が高い繰り返し波形ですが)を加えてからモニターし、最後に周波数領域に変換して、スペクトラムを 観測。まあ、ここまでは、理論通りだな。

それじゃ、フィルターを作るか。周波数領域でフィルターする事を意図します。一応ローパスフィルターをば。

def lf(a, fc=20):                             # low pass filter
       for n in range(SMPL):
           a[n] = 0+0j if fc <= n < (SMPL - fc) else a[n]
       return array(a)

カットオフ周波数20のローパスフィルターです。20以上の周波数データをゼロにして、無い事にして います。

wb> st | mix(tt(fs1=39,fs2=51)) | tap | fft | lf | ifft | getr | disp

シングルトーンの波形にノイズを混ぜ合わせ、その信号をfftして周波数領域へ持って行き、そこでフィルターを適用。 後は、ifftで時間領域へ戻して実部(の振幅成分)を 取り出すって事をやってます。

綺麗にノイズが消えて、正弦波に戻りました。そんじゃ、純正?なノイズを加えてみます。ノイズ発生器 は、まだ作ってなかったのでバラックで組んだ。本当は放射線の崩壊による方がいいんだけど。 ソフトウェアデザイン誌(SD)でもガイガーカウンターが取り上げられるぐらいだから、すっかりメジャーに なっちゃね。

def rnd(g=1):                                 # random data
    import random
    wave = []
    for n in range(SMPL):
        wave.append(g * (random.random() - 0.5))
    return array(wave)

wb> st | mix(rnd()) | tap | fft | lf | ifft | getr | disp

これ、ちょっと歪んでいるな。THDはどれぐらいだろう?

wb> st | mix(rnd()) | tap | fft | lf(fc=5) | ifft | getr | disp

フィルターの帯域を狭めてやると、ノイズの影響は軽減されて、元の波形に近づきますよ。

今日の実験台

だんだんとジャンク品が溜まってくるなあ。捨てるにはもったいないんで、まだそのままにしてるよ。

そうそう、変調器の出来具合も載せておく。シングルトーンを被変調波にした 周波数変調波位相変調波です。 上の周波数が512で打ち切りになってるのは、表示範囲を狭めているから。位相変調の位相が 拡大されているのは、オートスケールのおかげです。

from pylab import *
from numpy import *
from numpy.fft import *
from math import *
from cmath import phase

SMPL= 4096                                    # sampling points, must be 2**n
r2d = lambda r: 180 * r / pi                  # radian to degree
d2r = lambda d: d * pi  / 180                 # degree to radian
th  = lambda z: r2d(phase(z))                 # angle
getr = lambda a: array([x.real for x in a])   # real
geti = lambda a: array([x.imag for x in a])   # imag
geta = lambda a: array([abs(x) for x in a])   # power
gett = lambda a: array([th(x) for x in a])    # phase
mix  = lambda a,b: array((a + b))             # add array
mul  = lambda a,b: array((a * b))             # product array

def fm(fs, fc=300, m=0.5):                    # freq. modulation
    for n in range(SMPL):
        fs[n] = cos(2 * pi * (fc + m * fs[n]) * n / SMPL)
    return array(fs)

def pm(fs, fc=300, m=0.5):                    # phase modulation
    for n in range(SMPL):
        fs[n] = cos(m * fs[n] + 2 * pi * fc * n / SMPL)
    return array(fs)

def lf(a, fc=20):                             # low pass filter
       for n in range(SMPL):
           a[n] = 0+0j if fc <= n < (SMPL - fc) else a[n]
       return array(a)

def tap(a):                                   # tapping  
    figure()
    if True in iscomplex(a):
        #subplots_adjust(hspace=0.001) 
        subplot(211)
        f = geta(a)
        bar(arange(SMPL/8), f[:SMPL/8])        
        subplot(212)
        p = gett(nb(a))
        bar(arange(SMPL/8), p[:SMPL/8])
    else:
        plot(a)
    return array(a)

def disp(a):                                  # put on pipe end
    tap(a)
    show()

def nb(a, th=1e-4):                           # round to 0+0j when less th
    for n in range(SMPL):
        a[n] = 0+0j if abs(a[n]) < th else a[n]
    return a

def dt(a):                                    # display time domain
    figure()
    plot(a)
    show()

def df(a):                                    # display freq domain
    figure()
    #plot(a[:SMPL/2])
    #bar(arange(SMPL/2), a[:SMPL/2])
    bar(arange(SMPL/8), a[:SMPL/8])
    show()

def rnd(g=1):                                 # random data
    import random
    wave = []
    for n in range(SMPL):
        wave.append(g * (random.random() - 0.5))
    return array(wave)

def st(fs1=3, g=1.0):                         # single tone (for audio)
    wave = []
    for n in range(SMPL):
        wave.append(g * cos(2 * pi * fs1 * n / SMPL) )
    return array(wave)

def tt(fs1=3, fs2=5, g=0.8):                  # two tone (for audio)
    wave = []
    for n in range(SMPL):
        wave.append(g * (cos(2 * pi * fs1 * n / SMPL) +
                         sin(2 * pi * fs2 * n / SMPL)) )
    return array(wave)

def vo(fs1=2, fs2=3, fs3=4, fs4=5, g=0.7):    # like voice
    wave = []
    for n in range(SMPL):
        wave.append(g * (0.4 * cos(2 * pi * fs1 * n / SMPL) +
                         0.8 * sin(2 * pi * fs2 * n / SMPL) +
                         0.6 * cos(2 * pi * fs3 * n / SMPL) +
                         0.2 * sin(2 * pi * fs4 * n / SMPL)) )
    return array(wave)

def dd(msg='2133321331213116', m=0.5, s=-0.5): # dot dash (msg=JPL)
    wave = SMPL * [s]
    dots = 0
    for n in msg:
        if n == '1' or n == '3':
            dots += (1 + int(n))
        elif n == '2' or n == '6':
            dots += int(n)
        else:
            pass
    t = int(SMPL / dots)
    ref = [m] * 3 * t
    st = 0
    for n in msg:
        if n == '1' or n == '3':
            wave[st:st+t*int(n)] = ref[0:t*int(n)]
            st += (t * (int(n) + 1))
        elif n == '2' or n == '6':
            st += (t * int(n))
        else:
            pass
    return array(wave)

def am(fs, fc=300, m=0.5):                    # amplitude modurator
    for n in range(SMPL):
        fs[n] = (1 + m * fs[n] ) * cos(2 * pi * fc * n / SMPL)
    return fs

def dsb(fs, fc=300):                          # double side band
    for n in range(SMPL):
        fs[n] = fs[n] * cos(2 * pi * fc * n / SMPL)
    return fs

def run(wire):
    try:
        exlude = set(['show', 'show()', ])
        cmds = wire.split('|')
        for n, cmd in enumerate(cmds):
            cmd = cmd.strip()
            if (n == 0) or cmd in exlude:
                do_cmd = (cmd if cmd[-1] == ')' else (cmd + '()' ))
            else:
                do_cmd = (cmd.replace('(', '(val,', 1) if cmd[-1] == ')'
                          else (cmd + '(val)' ))
            val = eval(do_cmd)
        return val
    except Exception as e:
        print("Error: {0}".format(e))

def wb():                   #################### my REPL
    while True:
        run(raw_input('wb> '))

if __name__ == '__main__': wb()