vocaloidを自作しようとした話

最近、自分を実験台にして、いろいろデータを処理することにハマっています。

※前回記事もそんな感じです。

今回は、自分の声のデータを取って、周波数とかいろいろいじって、voicaloidを自作できたら面白そうだなって思ったので、作ることにしました。

とりあえず自分の声を録音する

Macbook Airを使っているので、GrageBandつかって適当に録音しました。
声の高さはなんでもいいのですが、録音している間は一定の高さで保っていた方が良いので、それを頑張るのがつらかった。(最近の運動不足で肺活量も落ちていることはまた別の話)
CD音質のwaveファイルを出力できるので、それを使おう〜!
とりあえず、「ん〜〜〜〜〜〜〜〜〜〜〜〜〜」(8秒くらい)って鼻歌歌ってる感じの声をとった。これをいじって鼻歌歌わせることから始めよう。

自分の声を分析してみる

まぁ分析って言っても、そう大したことはしません。

scipy.fft.fftに「ん〜〜〜〜〜〜〜〜〜〜〜〜〜」をつっこむと、こんな感じになります。

画像1

わ〜倍音だ〜(聞いたことある単語を言ってみただけ)

めっちゃ小さい成分を0にすることで、白色雑音を除きます。そのあとに、scipy.signal.argrelmaxを使ってピーク検出してあげると、以下の図のような感じになります。なんとなくorder = 10としました。ここらへんの処理は、ndarrayを使っていると楽にできると思います。

画像2

自分の声をいじろう!

(多分)ここが本題な気がします。音の3要素の「音色」についてはひとまず置いておいて、「高さ」「大きさ」をいじるところから始めます。

「大きさ」については、振幅をいじるだけなので省略。

「高さ」について、わざわざ(離散)フーリエ変換して周波数特性を求めたのには、
・収録した声の高さは、正の周波数のピークのうち、最小のものとみなせそう。
・周波数とその(相対的な)大きさを記録しておけば、フーリエ逆変換を使って、そこからいろんな高さの音を自在に作れそう。
っていう理由がありました。

さっそく、これを実装してみましょう!
scipy.fft.ifftで逆フーリエ変換すると、元の録音データを再現するだけなので、とりあえずフーリエ逆変換っぽいなにかを作っていきます。(正直合っている自信は全くない)

time = 2.0  # 2.0 秒間音を流す
rate = 44100  # CD 音質のサンプリング周波数です
dt = 1.0 / rate  # サンプリング周期
t = np.arange(0, time, dt)  # 時間(離散的データになるのでそれはそう)

# voice_freq は周波数の値, voice_fdata は周波数に対応する(相対的な)係数(複素数)
# 2つのindexが共通するようなデータを用意しています(日本語力皆無の説明)
# とりあえず、先述の方法を使って、ピークのところだけ取り出しました
w = np.outer(voice_freq, t)  # 周波数f * 時間t の行列(になっているはず)

voice_comp = np.dot(voice_fdata, np.exp(np.pi * 2 * -1j * w))  # 係数 * 周波数ごとの時間波形の和(になっているはず)
voice = np.real(voice_comp) # 実部のみとると、作りたい高さの声になっているはず

必要なライブラリをimportして、次のコードを実行すると、ちゃんと(?)元のデータに戻っている感じ(計算していないので分かりません)の図が出てきます。振幅がいろいろ違ってたりしても、どうせ後でいじるので無視します。

# a は、3cos(2pi*2t) + cos(2pi*5t) + sin(2pi*5t)をfftした結果 [周波数, 係数]
a = np.array([[2, 3], [-2, 3], [5, 1-1j], [-5, 1+1j]])
rate = 44100
time = 2.0
dt = 1.0 / rate
t1 = np.arange(0, time, dt)
w = np.outer(a[:, 0], t1)
c = np.dot(a[:, 1], np.exp(np.pi * 2 * 1j * w))
plt.plot(t1, np.real(c))
plt.xlabel("t")
plt.ylabel("y")
plt.show()

画像3

ここまでできれば、周波数をいじるのはめちゃくちゃ簡単です。wに(作りたい声の周波数) / (録音した声の周波数)をかければいいだけなのですから。

あとは、譜面を作って読み込ませられるようにして、それをうまくつなぎ合わせて曲を作るだけ!(だと、この時の私は思っていたのだった。)

曲再生、そして挫折

鼻歌が気持ち良さそうに聞こえる曲ってなんだろうな〜って思いながら、1フレーズ分歌わせてみることにしました。ほんのちょっとBPMが違っていますが、なんの曲でしょうね。(ヒント:アイコン)

hana 392.00 0.25 2
hana 392.00 0.0833333 2
hana 523.23 1 2
hana 523.23 0.25 2
hana 659.25 0.0833333 2
hana 783.98 0.1666666 2
hana 1046.5 0.1666666 2
hana 987.75 0.1666666 2
hana 879.99 0.1666666 2
hana 783.98 0.3333333 2
hana 659.25 0.25 2
hana 783.98 0.0833333 2
hana 698.45 0.3333333 2
hana 587.34 0.25 2
hana 659.25 0.0833333 2
hana 587.34 0.3333333 2
hana 659.25 0.25 2
hana 587.34 0.0833333 2
hana 523.23 1 2

譜面データとしては、1列あたり、[声の種類, 周波数, 秒数, 大きさ]というようにしました。鼻歌なので、"hana"、頭が悪そう。np.loadtxt(dtype = "str")で読み込ませてあげて、先述の通りに列ごとに音を作っていって、それらをつなげていけば、無事に自分の鼻歌が聞こえるはず・・・!!!

・・・

・・・・・・

・・・・・・・・・

・・・・・・・・・・・・

いや、メロディーはちゃんと聞こえるし、おお〜ってなるんですけど、これって自分の声って言えるのかな・・・
なんか、きれいになりました。
ひとまず、周波数をいじって、目的の高さに合わせられることは確認できたってことでいいかな。

いろいろ試行錯誤したのですが、1番私自身が歌ってるっぽい声室になった時の波形だけとりあえず置いておきます。(軸の値は適当だし、本当にこの図だったかも怪しい。)

画像4

感想

もぅまぢむり。(私自身の集中力のなさと思考力のなさと飽きっぽさに泣いています。)

自己相関関数を求めて周期を検出する、とかいろいろ他にも試してみたいこともあったけど、意外と時間がない(し、疲れてコーディングする気力が起きない)ので、ひとまず諦めることにしました。

鼻歌一つでいろいろ大変なことになっているので、「あ〜〜〜」とか「お〜〜〜」とかやったらさらにヤバそう。てか「わ〜〜〜」って伸ばすと結局「あ〜〜〜」になるし、いろいろ考えたら沼にハマりそう。実装方針が全然違うのかな・・・
初音ミクお姉様、どうやって作られたんでしょうか。気になりますね。
ていうか、人間の声質を決めている要素ってなんなんだろう。発する音ごとにそれっぽい周波数特性があるなら、ガラガラ声とか綺麗な声とかって、どうやって分かれるんだろう。ほんの少しの差なのかな。音声(というか信号というか)に関する知識も不足しているのでしばらく放置。残念だけどしょうがないね。3年以降の講義で学べることも多そうだし。

うまくいった〜とか、なんか進捗があれば、また記事を書くかもしれないです。

ではまた〜


この記事が気に入ったらサポートをしてみませんか?