はじめに
皆さん, 『あつまれ どうぶつの森』を楽しんでいますでしょうか. ほのぼのとした生活感は, 研究の辛さも忘れさせてくれません.
ところで, 4 月 4 日 新歓ブログリレー 16 日目 のとある記事は, このような言葉で締めくくられていました.
おいでよ らんだむのもり
というわけで, 今日を勝手に強制開発終了日として作ってみました.
♰『某ぶつの森風 ボイスメーカー』 (未完成) ♰
1. 環境
- Windows 10
- ffmpeg 4.2.2
- Python 3.7.3
主要モジュール
- gTTS 2.1.1
- pydub 0.23.1
- pyworld 0.2.8
- PyQt 5.14.2
2. 仕様
- テキストファイルまたはエディタ上に喋らせたいセリフを打ち込み, 各パラメータを設定して出力するだけ. 入力方式は
文章 <間隔 (秒)>
文章 <間隔 (秒)>
.....
-
再生, 停止ボタンは見せかけ. (バグで上手く再生されない)
-
下部のプログレスバー的なものはただそこにある矩形
パラメータの説明
speed : 速さ (default = 1.0)
speed 再生スピード
pitch : ピッチ (default = 1.0)
pitch 2 倍で 1 オクターブの変化
female : 女性らしさ (default = 1.0)
female フォルマントシフト
language : 言語 (default = Japanese)
後述する GTTS がサポートしている言語ならば扱える
default interval : 間隔の指定が無いときの値 (秒)
default interval
difficulty : 聞き取りづらさ (default = 3)
difficulty 高い値ほど, 音素が削れて詰まった声になる
3. デモンストレーション
{speed : 1.4, pitch : 1.5, female : 1.8, difficulty : 5}
実家:たべるんごのうた sm36210300
{speed : 1.4, pitch : 0.625, female : 1.4, difficulty : 5}
{speed : 1.6, pitch : 2.0, female : 2.0, difficulty : 3}
4. モジュールの紹介
gTTS
Google が提供している Google Text To Speakの Python モジュールバージョン.
import gTTS
serif = input()
gtts.gTTS(text=serif, lang='ja').save("output.wav") # 英語なら 'en'
pydub
MP3 や WAVE 形式などの音声ファイルを簡単に処理できる Python モジュール. 動かすのに ffmpeg が必要.
from pydub import AudioSegment
from pydub.playback import play
## 音声ファイルの読み込み
sound = AudioSegment.from_file("input.wav", "wav")
## 再生
play(sound)
結合やフェードイン / アウトなどが簡単に書ける.
head = sound[:5000] # 5 秒目までのデータ
tail = sound[5000:] # 5 秒目からのデータ
music = head + tail # 結合 sound と中身は一緒
## 1秒のフェードイン 2秒のフェードアウト
sound_2 = music.fade_in(1000).fade_out(2000)
無音部分で区切るのも簡単です.
from pydub.silence import split_on_silence
chunks = split_on_silence(sound, min_silence_len=1000, silence_thresh=-30, keep_silence=50)
pyworld
C++, matlab 用に作られた音声解析ライブラリ World の Python 用モジュール. 基本周波数やスペクトル包絡といった, ボイスチェンジャーに使うための音声の特徴量が簡単に取り出せて, 再合成できるのが特徴.
pydub で取り込んだデータは AudioSegment 型 で, pyworld で扱うためには np.float 型の ndarray にする必要がある.
import pyworld as pw
import numpy as np
for index, chunk in enumerate(chunks):
arr_chunk = np.array(chunk.get_array_of_samples()).astype(np.float)
## 以下, pyworld での処理
fs = chunk.frame_rate
_f0, t = pw.dio(arr_chunk, fs) # 基本周波数の抽出
f0 = pw.stonemask(arr_chunk, _f0, t, fs) # 基本周波数の修正
sp = pw.cheaptrick(arr_chunk, f0, t, fs) # スペクトル包絡の抽出
ap = pw.d4c(arr_chunk, f0, t, fs) # 非周期性指標の抽出
変更した特徴量からを音声を合成するには, synthesize メソッドを用います.
synthesized = pw.synthesize(new_f0, new_sp, new_ap, fs)
PyQt
Python で 気軽に GUI プログラミングを始められるフレームワーク. ギリギリになって GUI アプリにしようと思ったので殆ど理解してない.
5. ffmpeg
ffmpeg (エフエフエムペグ)とは, 簡単に言ってしまえば動画と音声の編集フリーソフトです. コマンド打つだけでいろいろやってくれます. セットアップ方法はコチラから. ちゃんと環境変数も通しておきましょう. これがあれば怪しいフォーマット変換サイトとおさらばできます.
6. 少し実装の話
ゲームの中の彼らが発する「どうぶつ語」(海外だと Animalese) のアルゴリズムは知る由もないので, (予め発音種類分のデータがあって, それにキャラクターや感情を載せている感じはする) 雰囲気で子音をいい感じに引っこ抜いて繋げることだけを意識した. 今回 gTTS を使ったのは, 発音種類分のデータを用意するのが面倒で, かつ用意したところでオートチューンで音程を補正するのが疲れそうだったからである. gTTS なら若干棒読みとはいえ, 十分にコスパが良いと判断した. 出力される音声が合成されるまでの過程は以下のようである.
- PyQt で適当に作ったガワから, 入力されたセリフやパラメータを抽出.
- gTTS でボイスを作成, 一旦保存.
- pydub を用いて無音部分を判定して区切る. (しきい値を渋めに設定すると子音の部分も少し削れる.)
- 区切られた要素ごとに, 頭のデータを捨て, 主に pyworld を用いてピッチやフォルマントの変更を行い, pydub で連結させる.
- (2 から 4) を 1 行ずつ行い, 最終的に pydub で連結させ, 再生スピードの調整をして出力.
実際書いてみるとほとんど何もしてないなと意気消沈しました. あと, 日本語は対応してませんが, 先駆者たちの Animalese Generator のリンクも張っておきます.
7. 問題点とかやり残したこと
- pydub で無音判定する際のパラメータに妥当性がなく, gTTSから得たデータの音量レベルが低いと分割が上手くいかない可能性がある.
- 上の問題もあり, difficulty パラメータにあまり意味がない気がする.
- 日本語基準の実装しかしてないから, 多言語対応しているけど上手くいかない方が多い.
- ノイズが入らないようにコンプレッサーかけたり, フェードイン / アウト させているけれど, たまにノイズが入ってしまう.
- 短いフレーズなどが入ると配列外参照してエラー吐くけどめんどくさかったからそのまま. (こら)
- スレッド処理してないから処理が遅く, GUI アプリが応答なしになる. そもそも理解してない.
- 特殊なトークンを置いて, この区間はハッキリ発音させたり, 声をこもらせたり, 多種多様なエフェクトを用意したかった.
- そもそも完成していない.
あと, 関係あるようでない話ですが, Kazuyuki Hiroshiba 氏のディープラーニング声質変換 OSS, Yukarin がとても面白そうだなと思いました. 頑張れば, バ美声界隈を牽引できるツールが作れそうです.
8. あとがき
声の生成, 再合成の部分の汚くて本当に酷いソースコードだけ公開しておきます. 実行してみたい人はどうぞ.
初めて合成音声処理をやってみましたが, 普段は DTM で当たり前のようにプラグインの恩恵を得ていたので, 偉大さを感じました. ありがとう.