Synthesis_voice

3183 days ago by takepwave

Hiroshi TAKEMOTO (take@pwv.co.jp)

音声合成のメカニズム in Sage

Interface 2013/11から連載が始まった「実験で入門!音声合成のメカニズム」をSageを使ってお復習いしていきます。

この説明は、私のブログの lbed/LM4F120 Launchpadで音声合成 と連携しています。 こちらも参考にしてください。

フーリエ級数

フーリエ級数は、周期的に繰り返される信号をSinとCosで表現したものです。

基本周期$T_0$として、周期信号を式で表すと次のようになります。 $$ f(t) = a_0 + \sum_{k=1}^\infty \left( a_k cos \frac{2 \pi k}{T_0} t + b_k sin \frac{2 \pi k}{T_0} t \right) $$

フーリエ級数の例

Interface 2013/11号では、以下の様な三角波を例題としています。この波形はY軸対称(偶関数)なので、求めるフーリエ係数の$b_k$が0となります。

Sageで三角波を表現する場合には、区分関数(Piecewse)を使用します。 三角波では、区間を-0.5から0と0から0.5の2つに分け、それぞれf1, f2で関数を定義します。

# [-0.5, 0.5]の区間で以下の関数を定義 n, t = var("n t") f1 = lambda t: 4*t + 1 f2 = lambda t: -4*t + 1 f = Piecewise([[(-1/2, 0), f1], [(0, 1/2), f2]]) plot(f, figsize=4) 
       

フーリエ係数を求める

基本周期$T_0$とするとフーリエ係数は、以下の様に求めることができます。 $$ \left\{ \begin{eqnarray} a_0 & = & \frac{1}{T_0} \int_{-T_0/2}^{T_0/2} f(t) dt \\ a_k & = & \frac{2}{T_0} \int_{-T_0/2}^{T_0/2} f(t) cos \frac{2 \pi k}{T_0} t dt, k = 1, 2, ... \\ b_k & = & \frac{2}{T_0} \int_{-T_0/2}^{T_0/2} f(t) sin \frac{2 \pi k}{T_0} t dt, k = 1, 2, ... \end{eqnarray} \right. $$

Sageでフーリエ級数を求める

さっそく、数式処理システムのSageを使って三角波のフーリエ級数を求めてみましょう。 残念ながらPiecewiseの関数に直接sin, cosを掛けることができないので、 sin, cosを掛けた区間関数を定義して、それを積分することにします。

以下がSageでの処理です。最後に$a_0, a_1, a_2, a_3, b_1$を出力しています。

この結果は、Interface 2013/11号の式(4)と同じ結果になっているのが確認できました。

$$ \left\{ \begin{aligned} a_k & = & \left \{ \begin{aligned} 0, & k: 偶数 \\ \frac{8}{(k \pi)^2}, & k: 奇数 \\ \end{aligned} \right. \\ b_k & = & 0, k: すべての整数 \end{aligned} \right. $$
# Piecewiseの関数にsin, cosを掛けることができないので(たぶんバグ)、 # フーリエ級数を求める積分は、sin, cosを掛けた区分関数を使って積分する T0 = 1 k = var('k') f1s = lambda k: f1(t)*sin((2*pi*k)/T0*t) f2s = lambda k: f2(t)*sin((2*pi*k)/T0*t) f1c = lambda k: f1(t)*cos((2*pi*k)/T0*t) f2c = lambda k: f2(t)*cos((2*pi*k)/T0*t) b(k) = (2/T0)*Piecewise([[(-1/2, 0), f1s(k)], [(0, 1/2), f2s(k)]]).integral(t, -T0/2, T0/2) a(k) = (2/T0)*Piecewise([[(-1/2, 0), f1c(k)], [(0, 1/2), f2c(k)]]).integral(t, -T0/2, T0/2) a0 = (2/T0)*Piecewise([[(-1/2, 0), f1c(0)], [(0, 1/2), f2c(0)]]).integral(t, -T0/2, T0/2) # a0 = Piecewise([[(-1/2, 0), f1], [(0, 1/2), f2]]).integral(t, -T0/2, T0/2) # とするとTypeErrorとなります(これもバグ?) # 値の確認 print a0, a(1), a(2), a(3), b(1) 
       
0 8/pi^2 0 8/9/pi^2 0
0 8/pi^2 0 8/9/pi^2 0

kの値を変えて波形を計算

求めた$a_k$を使ってkの値を1, 5, 19と変えた結果をプロットしてみます。

def fk(t, k): return sum(a(n)*cos((2*pi*n/T0)*t) for n in range(1, k+1)) show(fk(t, 3)) # n=1,5,19の合成波をプロット p = plot(fk(t, 2), -1,1, color='blue',legend_label='n=1') p += plot(fk(t, 5), -1,1, color='red',legend_label='n=5') p += plot(fk(t, 10), -1,1, color='green',legend_label='n=19') p.show(figsize=5) 
       

同じ問題をPiecewiseの関数で計算

Piecewiseには、フーリエ級数を計算する機能が備わっています。 先のfkに相当する関数がfourier_series_partial_sumです。

fourier_series_partial_sumの使い方は、以下の様に使います。

	g.fourier_series_partial_sum(N, L)
ここで、Nは計算する次数、Lは区間の長さL(区間は、-LからLです)で、fourier_series_partial_sumの返す値は、以下の式で表されます。 $$ f(x) \sim \frac{a_0}{2} + \sum_{n=1}^N [a_n\cos(\frac{n\pi x}{L}) + b_n\sin(\frac{n\pi x}{L})] $$

fourier_series_partial_sumで5次の項まで計算

それでは、同じ問題をfourier_series_partial_sumで計算してみましょう。

# 同じ問題をPiecewiseのフーリエ級数関数を使って解いてみる g = Piecewise([[(-1/2, 0), f1], [(0, 1/2), f2]]) plot(g, figsize=4) 
       
show(g.fourier_series_partial_sum(5, 1/2)) 
       

fourier_series_partial_sumのプロット

計算結果をプロットしてみます。N=100とするとほとんど三角波になっています。

p = plot(g.fourier_series_partial_sum(5, 1/2), -1,1, color='blue',legend_label='n=5') p += plot(g.fourier_series_partial_sum(10, 1/2), -1,1, color='red',legend_label='n=10') p += plot(g.fourier_series_partial_sum(50, 1/2), -1,1, color='green',legend_label='n=50') p += plot(g.fourier_series_partial_sum(100, 1/2), -1,1, color='purple',legend_label='n=100') p.show(figsize=5) 
       

フーリエ係数から波形の合成

Sageでフーリエ級数から波形を合成し、音として再生するために、scikits.audiolabをインポートします。

# scikits.audiolabからPySoundFileに変更 # from scikits.audiolab import wavread, wavwrite, Format, Sndfile import soundfile as sf 
       

「ア」のオリジナル波形

最初に「ア」のオリジナル波形をプロットします。

snOrg=[ -56, 139, 439, 785, 1041, 1277, 1180, 934, 564, 133, -147, -131, -9, 343, 684, 860, 991, 926, 773, 634, 501, 481, 695, 961, 1293, 1459, 1356, 1022, 613, 229, 118, 206, 437, 698, 794, 680, 430, 68, -192, -325, -365, -312, -278, -299, -350, -494, -731, -939, -1077, -1211, -1289, -1382, -1446, -1511, -1639, -1645, -1580, -1425, -1139, -808, -608, -420, -362, -288, -68 ] list_plot(snOrg, plotjoined=True, figsize=4) 
       

フーリエ係数から波形を合成関数synthesisの定義

フーリエ係数から波形を合成関数synthesisは、ほとんどCのsynthesisの実装と同じです。

def synthesis(an, bn, vn, order, nData): PI2 = 2.0*3.1415926 PI2_T = PI2/nData for n in range(nData): vn[n] = an[0] for k in range(1,order+1): kPi2T = k*PI2_T vn[n] += an[k]*cos(kPi2T*n) + bn[k]*sin(kPi2T*n) 
       

「ア」の合成

Interface 2013/11に出ている「ア」のフーリエ係数を使ってMAXの15次まで計算して合成した波形をプロットします。 オリジナルと遜色のないレベルまで再現できているのが分かります。

an = [20.61, -376.22, 305.13, 257.95, 45.26, -47.79, -205.22, -94.12, -8.32, -19.58, 19.21, -0.73, -2.51, 7.29, 0.97, -2.58] bn = [0.00, 949.63, 246.53, 181.33, 111.38, 38.06, 143.08, -269.01, 51.38, -34.27, 19.66, 13.44, 5.81, 5.36, -0.37, 3.62] vn = [0 for i in range(64)] N_TO = 64 synthesis(an, bn, vn, 15, N_TO) list_plot(vn, plotjoined=True, figsize=4) 
       

次数を変えて波形を合成してみる

Sageのインタラクティブモードを使って次数を変えて「ア」を合成してみましょう。

@interact def _(order=selector(range(1,16))): synthesis(an, bn, vn, order, N_TO) list_plot(vn, plotjoined=True, figsize=4).show() 
       

Click to the left again to hide and once more to show the dynamic interactive window

合成音を再生する

「ア」の波形を上手く合成できているのが、確認できましたので音として再生してみましょう。 ここでは、PySoundFileを使って8KHzの1チャンネルwavファイル形式で保存し、 それをブラウザーの機能を使って再生します。

合成された「ア」は少し短すぎるので、5回繰り返した波形sourcesを使います。

from numpy import abs, max # 5回繰り返した音を作る sources = [] sources = flatten([sources + vn for i in range(5)]) sources /= max(abs(vn), axis = 0) 
       

合成波形をwav形式で保存する関数として、recordSoundを定義します。 以降、この関数を使って合成波形を再生することにします。

def recordSound(sources, name, Hz): filename = DATA + name sf.write(sources, filename, samplerate=Hz) # 結果を確認する方法 html('<a href="%s">録音結果(右クリックでファイルをダウンロードして再生してください)</a>' % name) 
       

録音結果をクリックすると合成した波形が音として再生されます。ブラウザーによっては、 自動的に再生できないもののありますので、その時には右クリックでファイルをダウンロードしてから再生してください。