5
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

音声変換の進展(備忘録も兼ねて)

Last updated at Posted at 2022-02-12

備忘録も兼ねてるので結構雑なのは許してクレメンス。

これまでのお話

こんなことをやってたり、やっぱりダメだったのでCycleGan-VC2を参考にしたモデルで試したりしてた。
使ってた特徴量はメルケプストラム。この辺もCycleGAN-VC2を参考にして選択。

が、ダメ。

モデル調整を頑張ってみたり、メルケプストラムをNMFしてみたり。STFTしたもの(メルケプまで行かない)を入力してみたり、これもNMFしてみたり。なんか色々やったけど、終ぞ上手くいかず、そして萎えて断念。

今回のお話

断念してから1年。
ようやく新天地にたどり着いた。
前々から「LPCという特徴量がフィルタ的な役割でもある」とは聞いてた。音響管とかそういう話。
で、それを思い出し、調べ直した結果。このスライドの13枚め、「フォルマント合成 ~Formant Synthesis~」を見てビビッと来た。

フィルタってそういうことか

と。

こっからは早かった。
いそいそとpysptkをインストールし、チュートリアルドキュメントを見ながらコーディング。

前に用意しておいたペアデータ音声からそれぞれLPCを求め、音声1にLPC2を、音声2にLPC1を与えて音声を再合成。
するとどうだろう。なんと声質が入れ替わって聞こえるではないか!
しかしまだ安心はできない。CycleGANを使うことを考えると、ノンペアデータでもそうなる必要がある。
ということで、発話内容が違い、話者も違う音声2つを用意して同様の実験を行ってみた。
結果、声質はおろか、発話内容も維持されているように聞こえるじゃないか。

こりゃもう勝ったな。風呂入ってくる。

ということで、大きな進展がありました。という話。

プログラム

音声読み込み

難しいことは無い。scipyのwavfileを使用する。

from scipy.io import wavfile

x, fs = wavfile.read("filename.wav")

ついでに、長さを揃える場合はこんな感じ。

x1 = x1[fs1//2:fs1//2+fs1]

音声のフレーム化

要は窓関数を掛けたりする。

import librosa

def toFrames(x, frameLength=1024, hopLength=80, windowFunction=pysptk.blackman):
    frames = librosa.util.frame(x, frame_length=frameLength, hop_length=hopLength).astype(np.float64).T
    frames *= windowFunction(frameLength)
    return frames

今回はpysptkのチュートリアルに従ってブラックマンを採用。なんでブラックマンなのか、窓関数ごとの違いについては後で勉強しようと思う。

LPCを求める

基本的にはチュートリアルのままだが、np.apply_along_axis関数の調子が悪かった(というかpysptkの中でエラーを吐く)ので愚直にforループ使って実装。

import numpy as np
import pysptk

def toLPC(frames, order=25):
    for i, frame in enumerate(frames):
        frame += np.random.randn(len(frame)) * 10e-6
        if i == 0:
            lpc = pysptk.lpc(frame, order=order)
        else:
            lpc = np.vstack([lpc, pysptk.lpc(frame, order=order)])

    lpc[:, 0] = np.log(lpc[:, 0])
    return lpc

あとついでにf0も求めておく。

hopLength = 80
f0 = pysptk.swipe(x.astype(np.float64), fs=fs, hopsize=hopLength, min=50, max=500)

LPCを音声に再合成

ここも基本的にはチュートリアルのまま。

from pysas import excite

def toWave(x, fs, lpc, f0, output="synthed.wav", order=25, hopLength=80):
    generator = excite.ExcitePulse(fs, hopLength, False)
    sourceExcitation = generator.gen(f0)

    synthesizer = pysptk.synthesis.Synthesizer(pysptk.synthesis.AllPoleDF(order=order), hopLength)
    synthed = synthesizer.synthesis(sourceExcitation, lpc)
    wavfile.write(output, fs, synthed.astype(np.int16))

統合したもの

import numpy as np
import pysptk
from scipy.io import wavfile
import librosa
from pysas import excite


def toFrames(x, frameLength=1024, hopLength=80, windowFunction=pysptk.blackman):
    frames = librosa.util.frame(x, frame_length=frameLength, hop_length=hopLength).astype(np.float64).T
    frames *= windowFunction(frameLength)
    return frames


def toLPC(frames, order=25):
    for i, frame in enumerate(frames):
        frame += np.random.randn(len(frame)) * 10e-6
        if i == 0:
            lpc = pysptk.lpc(frame, order=order)
        else:
            lpc = np.vstack([lpc, pysptk.lpc(frame, order=order)])

    lpc[:, 0] = np.log(lpc[:, 0])
    return lpc


def toWave(x, fs, lpc, f0, output="synthed.wav", order=25, hopLength=80):

    generator = excite.ExcitePulse(fs, hopLength, False)
    sourceExcitation = generator.gen(f0)

    synthesizer = pysptk.synthesis.Synthesizer(pysptk.synthesis.AllPoleDF(order=order), hopLength)
    synthed = synthesizer.synthesis(sourceExcitation, lpc)
    wavfile.write(output, fs, synthed.astype(np.int16))


fs1, x1 = wavfile.read("sample/ssr1.wav")
fs2, x2 = wavfile.read("sample/tdm2.wav")
# x1 = x1[fs1//2:fs1//2+fs1]
# x2 = x2[fs2//2:fs2//2+fs2]

print(fs1, len(x1), len(x1) / fs1)
print(fs2, len(x2), len(x2) / fs2)

hopLength = 80
f01 = pysptk.swipe(x1.astype(np.float64), fs=fs1, hopsize=hopLength, min=50, max=500)
f02 = pysptk.swipe(x2.astype(np.float64), fs=fs2, hopsize=hopLength, min=50, max=500)

frames1 = toFrames(x1)
lpc1 = toLPC(frames1)

frames2 = toFrames(x2)
lpc2 = toLPC(frames2)

print(lpc1.shape, lpc2.shape)

toWave(x1, fs1, lpc2, f0=f02, output="synthed1.wav")

今後の予定

LPC使って変換するために学習モデルを構築する必要がある。
ので、とりあえず1次元畳み込みを使ってみようかな、と。
1チャンネルあたりが26とかの、ごく小規模なものだから効力が不安ではあるが、MNISTを1次元畳み込みでやってる記事があったので多分ダイジョブ。精度も悪く無さそうだし。

問題は、これを研究として発表なり論文投稿なりする場合、評価をどうするか。ただ聴き比べて「ほら、イイカンジでしょ?」はよろしくないらしい。学部のときに教授にこっぴどく言われた(卒研はそれで通してもらえたけど)。
なんか無いかねぇ。でもノンペアだしなぁ。LPCの類似度とか?PCA分析とかで圧縮して分布がどうなるか見る?
周波数分布でも何かできそうな気はするが。
まぁ、おいおい考えていきましょうかね。

5
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?