【試行錯誤】「〇〇で歌ってみた」動画の自動生成 その１：MusicXMLの歌詞を書き換えて歌声合成

# 概要
「〇〇で歌ってみたシリーズ」と呼ばれる替え歌動画の自動生成方法について試行錯誤してみます。
まずはMusicXMLの歌詞を書き換えて、NEUTRINOで替え歌の歌声合成をすることに挑戦しました。

シリーズ一覧：
[【試行錯誤】「〇〇で歌ってみた」動画の自動生成 リンクまとめ](https://qiita.com/shimajiroxyz/items/5df23a0ca81a3a2d9568)

# 背景
替え歌動画をつくることがときどきあるのですが、だいたい以下のような手順を踏みます。

* 替え歌歌詞を作成
* 歌声合成システムに替え歌を歌わせる
* 音楽に合わせて元歌詞、替え歌歌詞、適当な画像を適切なタイミングで表示する動画をつくる

これらの工程について、プログラムで楽をしたくなったので、できる範囲の自動化に挑戦してみます。

「替え歌歌詞の作成」については僕がよく作るジャンルの「〇〇で歌ってみたシリーズ」については[Soramimic](https://soramimic.com)という作詞支援システムを過去に作りました。

そこで今回は「歌声合成システムに替え歌を歌わせる」に挑戦してみます。

# 歌声合成システム
今まではVOCALOIDをつかってGUIベースで手打ちすることで替え歌の音声ファイルを作成していました。ただ手打ちは面倒なので、設定ファイルを読み込んでプログラムベースで歌声合成できれば、色々と楽ができそうです。

設定ファイルを読み込んでコマンドやプログラムから歌声合成できるソフトを探すといくつか候補が出てきたのですが、今回は[NEUTRINO](https://studio-neutrino.com/)を使ってみることにしました。MusicXMLという形式の設定ファイルを入力として歌声合成できるようです。他の候補である[NNSVS](https://r9y9.github.io/blog/2020/05/10/nnsvs/)や[Sinsy](https://www.sinsy.jp/)もほぼ同様のことができそうでしたが、前者よりもステップ数が少なそうなこと、後者と違ってローカルで実行できることにメリットを感じて採用しました。
（参考：もしNNSVSを使う場合は[こちら](https://gist.github.com/taroushirani/987211cc4023eab174b71bfe7041fd95)のやり方が参考になりそうでした）

# 実装
## NEUTRINOの実行
NEUTRINOはオンラインまたはローカルで動かすことができますが、今回はローカルで動かしてみました。OSはmacOSです。mac用のzipを公式からDLしてRun.shを実行したら行けました。

## MusicXMLの書き換え
MusicXMLの構造について以下サイトを参考にして少し理解を深めました。
[名曲の楽譜データをMusicXML形式で入手し、プログラムで可視化する方法](https://roomba.hatenablog.com/entry/2016/02/03/150354#MusicXML%E3%81%AE%E5%86%85%E5%AE%B9)
`part`に楽譜情報が書かれており、`measure`（小節）の単位で記述されているということだけ理解しました。

次にNEUTRINOに含まれるsample1.musicxmlを眺めてみます。と`<text>`というタグに歌詞がひらがなで書かれていることがわかります。

```xml
...
        <lyric number="1" default-x="6.58" default-y="-53.25" relative-y="-30.00">
          <syllabic>single</syllabic>
          <text>は</text>
          </lyric>
...
```

したがって、この`text`タグの部分を抽出して、文字を書き換えればよいです。
とりあえず`sample1.musicxml`の`text`の中身を全部取得してみます。

```Python
import xml.etree.ElementTree as ET
musicxml_path = "NEUTRINO/score/musicxml/sample1.musicxml"
tree = ET.parse(musicxml_path)
root = tree.getroot()
for v in root.findall("./part/measure/note/lyric/text"):
  print(v.text, end="")
```

```
はるがきたはるがきたどこにきたやまにきたさとにきたのにもきたはながさくはながさくどこにさくやまにさくさとにさくのにもさく
```

これを適当な替え歌にしてみます。
試しなのでなんでもよいのですが、例えば母音に変換してみます。
まず母音に変換するコードを雑に作ります。

```
pip install romkan
```

```Python
import romkan
def to_vowel(kana):
  roma_to_kana = {"a": "あ", "i": "い", "u": "う", "e": "え", "o": "お", "n": "ん"}
  vowel_roma = romkan.to_roma(kana)[-1]
  return roma_to_kana[vowel_roma]
print(to_vowel("は"))
```

```
あ
```

xmlを書き換えます。

```Python
import xml.etree.ElementTree as ET
musicxml_path = "NEUTRINO/score/musicxml/sample1.musicxml"
tree = ET.parse(musicxml_path)
root = tree.getroot()
# 歌詞を母音に書き換え
for v in root.findall("./part/measure/note/lyric/text"):
  v.text = to_vowel(v.text)

# 正しく変換できているか確認
for v in root.findall("./part/measure/note/lyric/text"):
  print(v.text, end="")
tree.write("output/vowel_sample1.musicxml", encoding="utf8") # encodingを指定しないと日本語が出力されない
```

```
あうあいああうあいあおおいいあああいいああおいいあおいおいあああああうああああうおおいあうああいあうあおいあうおいおあう
```
## 書き換えた歌詞で歌声合成
作成したmusicxmlを`NEUTRINO/score/musicxml/`に配置します。
次にRun.shのBASENAMEを配置したmusicxmlの拡張子を覗いた名前（vowel_sample1.musicxmlというファイルならvowel_sample1）に書き換えます。
そしてRun.shを実行すると合成が開始され、最終的にwavファイルができます。

## wavファイルのチェック
`NEUTRINO/output`にwavが作成されているので聴いてみます。
きちんと書き換えた歌詞で合成されていました。

# おわりに
無事、変換した歌詞で歌声合成することができました。
VOCALOIDとUTAU以外の歌声合成システムを初めて使ったのですが、NEUTRINO、簡単に使えて精度もよくてすごかったです。コマンドラインからmusicxmlを読み込んで使えるのも大変便利です。
次はMusicXMLから情報を抽出して、字幕動画を自動生成してみようと思います。

# 参考
[xml.etree.ElementTree --- ElementTree XML API](https://docs.python.org/ja/3/library/xml.etree.elementtree.html)