From Karaoke
女房に誘われて、久しぶりに からおけ へ行ってきた。入場料がわりに頼む必要 がある、ドリンク(430円)と、一人80円/時間の部屋代だけで済むとは、一体これで 儲かるのかと心配になる。
一曲毎に採点してしてくれるってのは、昔有ったかなあ? 今日の最高得点は、89 点だった。しつこく同じ曲を何度も歌ったけど、採点はぶれなかった。一体、どう いう仕組みなんだろう? ちょっと気になったぞ。後、曲毎に消費カロリーが 表示されてたけど、こちらは、ぶれが無い事からすると、DBにでも保存してるのを ただ表示してるだけだな。カロリーを気にするおばさん対策と思うぞ。
ちなみに、最高得点だった曲は、「鉄腕アトム」。この曲には思い出があって、 昔、東南アジアからの研修生受け入れの宴会で歌った時、大受けして彼らと 何度も歌ったんです。中国とか台湾にもしっかりアニメが浸透してるなあ。
と言う事で、少々音関係に興味が出てきた。マイクで音を拾って、波形を見る。 その波形を、周波数ドメインで見るなんて事をやってみたい。
波形の作成
が、マイクは、生憎持ち合わせていないので、取り合えずサイン波を合成してやればいいだろう。 どうせやるなら、昔学校で習った、AM放送の原理を再現してみる。 さて、言語は何にしよう? 最近触っていない ruby でやってみる。
#!/usr/local/bin/ruby
DATAN = 256 # Wave size
Sin_amp = { # Table of Freq vs Amp, freq => amp
3 => 0.3,
}
Car = 64
def cal_point(n)
k =0.0
Sin_amp.each do |freq, amp| f = freq.to_i
k = 1 + amp * Math::sin(2 * Math::PI * f * n / DATAN)
end
val = k * Math::cos(2 * Math::PI * Car * n / DATAN)
val
end
DATAN.times do |n|
printf("%.6f\n", cal_point(n) )
end
TBSなら954KHzだけど、このプログラムでは64(と言う、搬送波)、アナウンサーの声 の変わりに、周波数3と言う信号波、振幅は、搬送波に対して0.3倍(だから、変調率で言うと、 0.3で、良かったかな? 中学生の時、アマチュア無線の資格を取った時以来だから 、ちょっと自信なさげ)
出力は、電波じゃなくて、標準出力へ放出される。
FFT するぞ
次は、スペクトラムアナライザーの心臓部、FFTだ。うーん。FFTねぇ。ネットを 探してみると、rubyだけで書かれた例も見つかったけど、上と同じRubyを使うのも なんだかつまんないので、別の路線を考える。
思い出したのが、「プログラミング言語SCHEME」に出ていた例 これを頂いてきて、読み込みルーチンを付け加えてやろう。最近ご無沙汰してる gaucheを使う事にする。
#!/usr/local/bin/gosh
;;; Original by fft.ss
;;; Copyright (C) 1996 R. Kent Dybvig
;;; from "The Scheme Programming Language, 2ed" by R. Kent Dybvig
(define DATA_FILE "wavein.txt")
(define (w-powers n)
(let ((pi (* (acos 0.0) 2)))
(let ((delta (/ (* -2.0i pi) n)))
(let f ((n n) (x 0.0))
(if (= n 0)
'()
(cons (exp x) (f (- n 2) (+ x delta))))))))
(define (evens w)
(if (null? w)
'()
(cons (car w) (evens (cddr w)))))
(define (interlace x y)
(if (null? x)
'()
(cons (car x) (cons (car y) (interlace (cdr x) (cdr y))))))
(define (split ls)
(let split ((fast ls) (slow ls))
(if (null? fast)
(values '() slow)
(call-with-values
(lambda () (split (cddr fast) (cdr slow)))
(lambda (front back)
(values (cons (car slow) front) back))))))
(define (butterfly x w)
(call-with-values
(lambda () (split x))
(lambda (front back)
(values
(map + front back)
(map * (map - front back) w)))))
(define (rfft x w)
(if (null? (cddr x))
(let ((x0 (car x)) (x1 (cadr x)))
(list (+ x0 x1) (- x0 x1)))
(call-with-values
(lambda () (butterfly x w))
(lambda (front back)
(let ((w (evens w)))
(interlace (rfft front w) (rfft back w)))))))
(define (fft x)
(rfft x (w-powers (length x))))
;;; below line's add by S.K. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (make-list file)
(define (build lis x fp)
(if (eof-object? x)
(reverse lis)
(build (cons x lis) (read fp) fp)))
(call-with-input-file file
(lambda (fp)
(build '() (read fp) fp))))
(define (other-way-make-list file) ;; Care order of val list
(with-input-from-file file
(lambda ()
(port-fold-right (lambda (x lis) (cons x lis)) '() read))))
(define (do-fft file)
(let ([x (make-list file)])
(for-each (lambda (v) (format #t "~s\n" (abs v)))
(fft x))))
(do-fft DATA_FILE)
入力は、ファイル渡しで、出力は、標準出力に出す事にした。
表示部の作成
次は、結果の表示だ。グラフを書く事になるので、スマートに済ませてしまおう。 Rを使って、一機にpngファイルを作る事にする。
#!/usr/local/bin/Rscript
# pdf(file="Rplots.pdf")
png(file="Rplots.png")
par(bg="grey95", mar=c(4,4,1,1), mfrow=c(2,1))
wave <- read.table("wavein.txt")
plot(wave$V1, type="l")
spec <- read.table("specout.txt")
n <- length(spec$V1)
s <- spec$V1[1:(n/2)]
plot(s/n, type="h")
dev.off()
お好みで、PDFにも吐き出せるぞ。
全部をまとめてみる。
さて、部品は揃った。後は、これらを結合するんだけど、相場は shell を使う んだろうけど、後々の事(拡張)を考えて、Makefileにしておく。 そうすれば、make am とか(今回)、FM波形が必要なら、適当に波形を合成して make fm とするだけだ。
am: ./AMgen.rb > wavein.txt ./FFT.scm > specout.txt ./disp.R xv Rplots.png #acroread Rplots.pdf
結果発表
某メーカーもびっくりの、よくばり表示器です。
参考にデジタル信号処理 を上げておきます。