神野さんに言われました。

読者です 読者をやめる 読者になる 読者になる

神野さんに言われました。

AIの勉強をしています @sesenosannko

ソースフィルタモデルのソースってなんだ

ソースフィルタモデルのソースって、そもそもなんなのか。

tam5917.hatenablog.com

これによると

声帯音源の特徴は有声音と無声音で異なる。ただし有声音とは声帯の振動を伴う音声であり、無声音は声帯の振動を伴わない音声である。声帯の振動により発生する「音」をパルス列で表現し、それ以外は白色雑音で表現する。この段階では音声ではない。パルス列は要はブザー音である。音の大きさはパルスの振幅の大きさできまり、音の高さはパルスの間隔で決まる。間隔が密なほど音が高く、疎なほど音は低く聞こえる。つまり間隔の逆数に比例して高さが決まる。そのパルスの間隔に対応し、音の高さを表す特徴量が基本周波数である。

よく聞くことがキレイにまとまって書いてありますね。
パルス列っていうのがなんなのかはいまいちわかりません。
たぶんすごく基礎的なことなのでしょう。

aidiary.hatenablog.com

結局これに戻ってきてやってます。
とりあえず、これを実行します。
コードがあまりに汚いという意見はちょっとよく聞こえないです。
ちなみに、読めばわかりますが元データがa01.rawという名前でカレントに入ってる前提です。

import numpy as np
from pylab import *
import os
from scipy.io.wavfile import read as sread

os.system('x2x +sf a01.raw | pitch -a 1 -p 80 -s 16 -L 60 -H 240 > a01.pitch')

os.system('dmp +f a01.pitch > a01.txt')

f = open('a01.txt')
c = f.read()
f.close()

c = c.split('\n')
c = [c[i].split('\t') for i in range(len(c))]

del c[len(c) - 1]

for i in range(len(c)):
	c[i][0] = int(c[i][0])
	c[i][1] = float(c[i][1])

figure()
x = [c[i][0] for i in range(len(c))]
y = [c[i][1] for i in range(len(c))]
plot(x, y)
show()

os.system('excite -p 80 a01.pitch | sopr -m 1000 | x2x +fs > source.raw')
os.system('raw2wav source.raw')

fs, data = sread('source.wav')
x = [i+1 for i in range(len(data))]

figure()
plot(x, data)
show()

でてくるグラフが下の二枚です。
一枚目がピッチ、二枚目がソースですね。
上の「人工知能に関する断創録」さんの記事にも書いてありますが、両者の形が似ています。
というか、たぶんピッチを何かして(単純に実数倍ではないみたい)振幅が決まっているのでしょう。
この辺りがよく分からないポイントですね。
ピッチの値がないところに関しては白色雑音で表現されているみたいですね。

f:id:sesenosannko:20160515231908p:plain
f:id:sesenosannko:20160515231912p:plain

で、分からない分からない言っていても始まらないので、ちょっと考えてみます。
下のやつが元データATR503文の1個目、「あらゆるの現実をすべて自分の方へ捻じ曲げたのだ」の波形です。

f:id:sesenosannko:20160515231916p:plain

これを拡大すると、おなじみのこんな感じです。

f:id:sesenosannko:20160515232907p:plain

ここで、ソースの方は拡大するとこんな感じになっています。

f:id:sesenosannko:20160515232929p:plain

パルス列!!!
パルス列ってこういうことだったんですね・・・・
めっちゃ基礎的なことなんだろうなぁ・・・

ちなみに、横に拡大するとこんな感じで、パルス列の密度が場所によって異なっていることがわかります。
おそらくピッチに従っているのでしょう。
そうなると、振幅がピッチと同じような形を描く理由がさらによく分からないですが、それはそういうことなんだということにしておきましょう。とりあえず。
知っている人がいたら教えてください。

f:id:sesenosannko:20160515233019p:plain

パルスっていうことは、つまりデルタ関数みたいに考えていいんじゃないですか。たぶん。
春休みに離散フーリエ変換するコードを組んでいたのでとりあえずやってみます。
コードはこんな感じです。

www.amazon.co.jp

これの内容を参考に作りました。
実際の音源では虚数部がないということで、割と簡単に組めるっていう仕組みですね。
ただ、ちょっと今日はあんまり中身を見直す時間がなかったので、なんかおかしかったらすいません。

import wave
import math
import array
from pylab import *
from scipy.io.wavfile import read

# filenameに指定されたwavファイルをDFTして振幅スペクトルを出力
#N = 64
N = 1024

filename = raw_input('filename:')
fs, sreal = read(filename)
if len(shape(sreal)) > 1:
	sreal = [sreal[i][0] for i in range(len(sreal))]

sreal = sreal[2400:]
sreal = [float(i) for i in sreal]

# ハニング窓の生成
if N % 2 == 0:
	# Nが偶数
	w = [0.5 - 0.5 * math.cos(2.0 * math.pi * n / N) for n in range(N)]
else:
	# Nが奇数
	w = [0.5 - 0.5 * math.cos(2.0 * math.pi * (n + 0.5) / N) for n in range(N)]

for i in range(N):
	sreal[i] = sreal[i] * w[i]

sreal = [i / 32767 for i in sreal]
simag = [0.0 for i in range(N)]

xreal = [0.0 for i in range(N)]
ximag = [0.0 for i in range(N)]

for k in range(N):
	for n in range(N):
		wreal = math.cos(2.0 * math.pi * k * n / N)
		wimag = - math.sin(2.0 * math.pi * k * n / N)
		xreal[k] += wreal * sreal[n] - wimag * simag[n]
		ximag[k] += wreal * simag[n] + wimag * sreal[n]

for k in range(N):
	print "%d %f" % (k, math.sqrt(xreal[k] ** 2 + ximag[k] ** 2))

filename = filename.split('/')
filename = filename[-1]

xplot = [k for k in range(N / 2)]
yplot = [math.sqrt(xreal[k] ** 2 + ximag[k] ** 2) for k in range(N / 2)]
figure()
for k in range(N/2):
	plot([k, k], [0, yplot[k]])
title(filename)
xlabel('frequency')
ylabel('amplitude spectrum')
show()

これで出すとこうなります。
まずは元データ。

f:id:sesenosannko:20160515234500p:plain

まぁなるほどって感じです。
次にソースです。

f:id:sesenosannko:20160515234523p:plain

ふむ・・・
信号論をかなり忘れてしまったのでもうよく分からないんですが、デルタ関数だとすべての周波数を等しく含んでるっていう感じの話だと思います。
ちょっとまたもう少し調べますが、基本的には、全周波数の情報を含んでいるソースに、周波数特性を含んだフィルタをかけているということなのでしょう。
また今度、フィルタの記事を書くのでそれまでには理解したい。
パルスの間隔っていうのがどう影響がでてくるんだったかなぁ・・・
ちょっとね、また調べます(それしか言ってない)