Edited at

【Scipy】FFT、STFTとwavelet変換で遊んでみた♬~音声合成アプリ

前回は音声分離アプリを作成したが、今回は合成アプリを作ってみよう。音声の分割と合成ができると、例えば「あ、い、う、え、お」と「あ、か、さ、た、な、は、ま、や、ら、わ」を取得し、「k、s、t、。。。」を分離して、組み合わせると、五十音が合成できるはずである。


とりあえずの目標

以下のとおり、

※リンクされているものは記事にしたものであり、その記事を前提知識として書いて行くので、参照すると理解し易いと思います。

Scipy環境を作る

 ・環境の確認

不確定原理について

・FFT変換・逆変換してみる;時間軸が消える

・STFT変換・逆変換してみる;窓関数について

・wavelet変換・逆変換してみる;スペクトログラムの解釈と不確定原理

音声や地震データや株価や、。。。とにかく一次元の実時系列データに応用する

音声データ入力編

FFTからwavelet変換まで簡単にたどってみる(上記以外のちょっと理論)

⑤二次元データに応用してみる

⑥天体観測データに応用してみる

リアルタイムにスペクトログラムしてみる

高速化(バグあり)

高速化完成版

音声分離アプリ


コードは以下に置きました

Scipy-Swan/add_wav.py


やったこと

・音声の合成「おはよう」「開けゴマ」

・音声の合成「あいうえお」「k」

・コード解説と演算して合成


・音声の合成「おはよう」「開けゴマ」

以下は、単純に「おはよう」と「開けゴマ」の合成結果である。

以下のGitHubに合成した音声データを置いた。IEだと、クリックすると生成できる。

※ほかのブラウザだと再生できないようだ?

Scipy-Swan/data/ohayohirakegoma_out.wav

ohayohirakegoma_out.jpg


・音声の合成「a,i,u,e,o」「k」

ka;Scipy-Swan/data/ka_out2.wav

ka_out.jpg

ka_out2.jpg

ki;Scipy-Swan/data/ki_out2.wav

ki_out.jpg

ki_out2.jpg

ku;Scipy-Swan/data/ku_out2.wav

ku_out.jpg

ku_out2.jpg

ke;Scipy-Swan/data/ke_out2.wav

ke_out.jpg

ke_out2.jpg

ko;Scipy-Swan/data/ko_out2.wav

ko_out.jpg

ko_out2.jpg


・コード解説と演算して合成

一応、上記でもやりたいことはやれたような気もするが、よく聞くと[ke]と[ko]は[e]と[o]が強すぎて、「け」「こ」よりは「kえ」「kお」と聞こえるように思う。

コードはちょっと長いけど、以下のとおり

おまじないは、いつもの通り

#coding: utf-8

import wave
import pyaudio
import matplotlib.pyplot as plt
import numpy as np
import time
import wave
from scipy.fftpack import fft, ifft
from scipy import signal

以下は読み込んだwavファイルの諸量を出力する関数

def printWaveInfo(wf):

"""WAVEファイルの情報を取得"""
print( "chn:", wf.getnchannels())
print( "width:", wf.getsampwidth())
print( "sampl rate:", wf.getframerate())
print( "no. of frame:", wf.getnframes())
print( "parames:", wf.getparams())
print( "length(s):", float(wf.getnframes()) / wf.getframerate())

以下は、前回と同じく生データ、STFT、FFTの描画関数

縦軸の大きさが変わるので、今回は固定解除した。

def plot_wav(filename,t1,t2):

ws = wave.open(filename+'.wav', "rb")
ch = ws.getnchannels()
width = ws.getsampwidth()
fr = ws.getframerate()
fn = ws.getnframes()
fs = fn / fr
print(fn,fs)
origin = ws.readframes(fn)
data = origin[:]
sig = np.frombuffer(data, dtype="int16") /32768.0
t = np.linspace(0,fs, fn, endpoint=False)
fig = plt.figure(figsize=(12, 10))
ax1 = fig.add_subplot(311)
ax2 = fig.add_subplot(312)
ax3 = fig.add_subplot(313)

ax1.set_xlabel('Time [sec]')
ax1.set_ylabel('Signal')
#ax1.set_ylim(-0.0075,0.0075)
ax1.set_xlim(t1,t2)
ax1.plot(t, sig)

nperseg = 1024
f, t, Zxx = signal.stft(sig, fs=fn, nperseg=nperseg)
ax2.pcolormesh(fs*t, f/fs/2, np.abs(Zxx), cmap='hsv')
ax2.set_xlim(t1,t2)
ax2.set_ylim(20,20000)
ax2.set_yscale('log')

freq =fft(sig,int(fn))
Pyy = np.sqrt(freq*freq.conj())*2/fn
f = np.arange(20,20000,(20000-20)/int(fn)) #RATE11025,22050;N50,100
#ax3.set_ylim(0,0.000075)
ax3.set_xlim(20,20000)
ax3.set_xlabel('Freq[Hz]')
ax3.set_ylabel('Power')
ax3.set_xscale('log')
ax3.plot(f*fr/44100,Pyy)

plt.savefig(filename+'.jpg')
plt.show()

plt.close()
ws.close()

以下は、今回複数ファイルを使うので、wavファイルの読み込み関数を別途定義

def fileOpen(filename):

wf = wave.open(filename+".wav", "r")
printWaveInfo(wf)
fr = wf.getframerate()
fn = wf.getnframes()
fs = float(fn / fr)
# ストリームを開く
p = pyaudio.PyAudio()
stream = p.open(format=p.get_format_from_width(wf.getsampwidth()),
channels=wf.getnchannels(),
rate=wf.getframerate(),
output=True)
CHANNELS = wf.getnchannels()
width = wf.getsampwidth()
return wf,stream,fr,fn,fs,width,CHANNELS

合成する2つのファイル名を入力して上記関数で読み込む

if __name__ == '__main__':

filename1=input('input original filename=')
wf1,stream1,fr1,fn1,fs1,width1,CHANNELS1 = fileOpen(filename1)

filename2=input('input original filename=')
wf2,stream2,fr2,fn2,fs2,width2,CHANNELS2 = fileOpen(filename2)

合成する二つの音声の大きさfc1.fc2を入力する。

ここで、numpyを使って、g1,g2に変換することにより、演算できるようにする。

また、それぞれ読み込んだwavファイルの音声をstream1.write(g1)により出力している。

    fc1=int(input('input factor1='))

fc2=int(input('input factor2='))

frames = []
for i in range(0, int(fr1 / 1024 *fs1+0.5)):
data = wf1.readframes(1024)
g1 = fc1*np.frombuffer(data, dtype= "int16") #/32768.0 # -1~1に正規化 #g1は演算できる
frames.append(g1)
stream1.write(g1)

for i in range(0, int(fr2 / 1024 *fs2+0.5)):
data2 = wf2.readframes(1024)
g2 = fc2*np.frombuffer(data2, dtype= "int16") #/32768.0 # -1~1に正規化 #g2は演算できる
frames.append(g2)
stream2.write(g2)

新たなファイルに合成した音声を保存する。

    loff1 = wf1.getnframes()/1024  #len(frames)

loff2 = wf2.getnframes()/1024 #len(frames)

wr = wave.open('wav/'+filename1+filename2+'_out.wav', 'wb')
wr.setnchannels(CHANNELS2)
wr.setsampwidth(width2) #width=2 ; 16bit
wr.setframerate(fr2)
s=int((loff1+loff2)*(fs1+fs2)/(fs1+fs2))
wr.writeframes(b''.join(frames[0:s]))
#wr.close()

そして、保存したwavファイルの画像を出力する。

    fn = wr.getnframes()

fs = float(fn / wr.getframerate())
print(fn,fs)
plot_wav('wav/'+filename1+filename2+'_out',0,fs)

合成した音声から前半部分と後半部分の音声をそれぞれ切り出して、合成・保存する。

    t1=float(input('input t1='))

t2=float(input('input t2='))
t3=float(input('input t3='))
t4=float(input('input t4='))

wr = wave.open('wav/'+filename1+filename2+'_out2.wav', 'wb')
wr.setnchannels(CHANNELS2)
wr.setsampwidth(width2) #width=2 ; 16bit
wr.setframerate(fr2)
s1=int((loff1+loff2)*(t1)/(fs1+fs2))
s2=int((loff1+loff2)*(t2)/(fs1+fs2))
s3=int((loff1+loff2)*(t3)/(fs1+fs2))
s4=int((loff1+loff2)*(t4)/(fs1+fs2))
print(t1,t2,t3,t4,s1,s2,s3,s4)
wr.writeframes(b''.join(frames[s1:s2]))
wr.writeframes(b''.join(frames[s3:s4]))
#wr.close()

切り出した合成音の生データ、STFT、そしてFFT画像を出力する。

    fn = wr.getnframes()

fs = float(fn / wr.getframerate())
print(fn,fs)
plot_wav('wav/'+filename1+filename2+'_out2',0,fs)

stream1.close()
stream2.close()
exit()

ということで、[k][o]の合成する前に大きさを変えて合成するというのを試みた。


「おはよう」*5+「開けゴマ」*2

ohayo*5hirakegoma*2;Scipy-Swan/data/ohayohirakegoma_out2.wav

ohayohirakegoma_out2.jpg


[k]*3+[o]*2

k3o2;Scipy-Swan/data/k3o2_out2.wav

ko_out2.jpg


まとめ

・音声合成アプリを作ってみた

・[k]+[aiueo]から[ka][ki][ku][ke][ko]の合成が出来た

・合成する二つの音声を演算して合成できるようにした

・自動的に分離・合成ができていない