Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[project-s] 歌ボ形式で歌声合成する機能を追加 #1255

Merged
merged 8 commits into from
Apr 11, 2023

Conversation

sigprogramming
Copy link
Contributor

@sigprogramming sigprogramming commented Mar 19, 2023

内容

歌ボ形式で歌声合成(レンダリング)する機能を追加します。

  • スコアを休符で区切って複数のフレーズに変換
  • フレーズごとに以下を行う
    • シンガーとフレーズ部分のスコアを元にクエリを生成
      • ノートとモーラの数が一致しない場合は例外をスロー
    • シンガーとクエリを元に音声を合成
    • フレーズ部分のスコアとクエリを元にフレーズの開始時刻を計算
  • 各フレーズの音声をミックスして再生
    • 音声合成が完了するまではシンセで再生(無音でもいいと思います)
  • シンガー・スコア変更時に変更部分のみレンダリング
    • タスク(推論や音声合成)は一つずつ実行
    • レンダリング中に編集が行われた場合は、タスク終了後に再レンダリングを行う
    • アプリケーション終了時に再生とレンダリングを停止

長いノートは、長音で伸ばすなど色々試したのですが上手く合成できなかったので、
とりあえずノート自体を短くするようにしています。
また、高い音(C5くらいから)も上手く合成できませんが、
とりあえずノートナンバーから算出した値をそのままピッチに設定しています。

関連 Issue

ref #1254

Copy link
Member

@Hiroshiba Hiroshiba left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ほぼLGTMです!!!

いろいろコメントしていますが、必ず直すべきみたいな部分はなかったと思います!


将来的な設計の話ですが、「合成し終わってから再生」したいユーザーも居るかもとかちょっと思いました!
その場合今の設計だと「合成し終わっているか」を取得しにくいかも?と思いました。

これはただの感想ですが、Phrase作成即合成で再生までの時間を縮めようとすると、合成のキャンセル機能が欲しくなりますね・・・ 😇

Comment on lines +285 to +288
constructor(audioPlayer: AudioPlayer, audioEvents: AudioEvent[]) {
this.audioPlayer = audioPlayer;
this.audioEvents = audioEvents;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

なるほどです、設計がなんとなく見えてきた気がします!

AudioSequenceがAudioPlayerを持つ設計ではなく、AudioSequenceを持つ誰かがAudioPlayerを1つだけ持つ設計にすると、Playerが散らばらずに1箇所に集まるので管理しやすい・・・かも・・・・・?
いやでも一様に扱えるので今の設計もありかも・・・?

まあ設計をガチャガチャ変えながら実装するよりも、一旦とりあえず全部実装してみてあとで整理して再設計するのが良いのかなと思いました。
なのでとりあえず問題なく動いているなら今は一旦良し・・・!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Transportで全てのイベントのスケジューリングを行うためにこの形になっていますが、私も「AudioSequenceを持つ誰かがAudioPlayerを1つだけ持つ」形のほうが自然な気がするので、後でまた設計を検討・試行したいと思います!
おそらく「AudioSequenceを持つ誰か」がそれぞれスケジューリングを行うことになり、そのスケジューリングの実行をTransportが管理することになると思います。
(一応今の設計でもループやマルチトラックの実装は可能です)

src/store/singing.ts Outdated Show resolved Hide resolved
src/store/singing.ts Outdated Show resolved Hide resolved
src/store/singing.ts Outdated Show resolved Hide resolved
src/store/singing.ts Outdated Show resolved Hide resolved
src/store/singing.ts Outdated Show resolved Hide resolved
src/store/singing.ts Show resolved Hide resolved
src/store/singing.ts Outdated Show resolved Hide resolved
src/store/singing.ts Outdated Show resolved Hide resolved
src/store/singing.ts Show resolved Hide resolved
src/store/singing.ts Show resolved Hide resolved
src/store/singing.ts Show resolved Hide resolved
src/store/singing.ts Outdated Show resolved Hide resolved
src/store/singing.ts Show resolved Hide resolved
src/store/singing.ts Outdated Show resolved Hide resolved
Copy link
Member

@Hiroshiba Hiroshiba left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!!

データ構造が入り組んできたなというのが正直な感想です!
ぶっちゃけどういう構造が良いのかわからないので、とりあえず動くものを完成させてからいろいろ議論するのが良いのかなと思いました!

「ストレスフリーに動くようにする」というラスボスタスクが現れるまでは、まあだましだまし進めていくのが良いのかな~と。
(そういう意味で、Phraseも3段階キャッシュ構造をなくしても良いかもとちょっと思いました。このあたりはかなり理解できてる気がするので、どちらでも・・・!)

@sigprogramming
Copy link
Contributor Author

@Hiroshiba
レビューありがとうございました!
私もデータ構造が入り組んでいると感じたので、レンダリング周り、一旦整理してみたいと思います…!

3段階キャッシュ構造をなくしても良いかも

ひとまずaudioQueryCacheを削除して、allPhrasesとaudioBufferCacheのみにしてみました。

「合成し終わっているか」を取得しにくいかも

再生時のUIの更新などで、どこかでポーリングすることになるので、そこで「合成し終わっているか」を判定しても良いかもと思っています。
(「合成し終わっているか」の判定はUIのロジックではないので、UIの更新と分けても良いと思います)
AudioRenderer側(スケジューリングのループ)で判定する場合は、「再生可能なAudioEventかどうか」を判定する形になると思います。

@Hiroshiba
Copy link
Member

再生時のUIの更新などで、どこかでポーリングすることになるので、

なるほどです!
数百のオブジェクトがポーリングしまくる感じにはならなそうなので、いいのかなと思いました!

@Hiroshiba
Copy link
Member

Hiroshiba commented Apr 7, 2023

ひとまずaudioQueryCacheを削除して、allPhrasesとaudioBufferCacheのみにしてみました。

直前で作っている形で、コードが読みやすくなって一旦良いのかなと思いました!!


これはただの所感なのですが、データ構造は「全体scoreからたくさんのphraseが作られる」というのが今の形だと思うのですが、「たくさんのphraseがあって全体scoreが作られる」形にしていくのが良いのかなとちょっと感じています。

ユーザーが操作するのは全体scoreではなくphraseである、みたいな。
編集したノートが所属するphrase、あるいは所属すべきphraseを算出し、そのphraseのscoreなりnotesなりのノートを編集し、必要とあらばphraseを分割したり結合したりする、とか・・・!

Copy link
Contributor

@romot-co romot-co left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sigprogramming
大変遅くなり失礼いたしました、LGTMです!!

また、おてすきで以下でもし大きく動作把握誤りがあればおしらせください…!
(しっかり読めていないやもしれず…)


Transport: 各Sequence(Note/Audioの両方)のスケジューリング・イベント管理
AudioSequence: ボイス用Sequence/イベントの集合
Phrase: エンジン側に渡すクエリ・再生タイミング・オーディオバッファ


RENDERの動作:

  • (プリプロセス/重複ノートの削除や長いノートの修正など)
  • Phrase群の更新
  • Phraseのクエリ(ハッシュ)が追加・変更されていたらクエリを(再)作成し合成
  • RENDERのdispatch時にすでにレンダリングが行われていた場合、中止し新しいリクエストからレンダリング

上記から、以下のタイミングで再合成される(Noteの場合はPhrase単位で該当部のみ変更)

  • Singer変更
  • Score作成・変更
  • Note追加・変更・削除
  • Tempo変更

再生やシーク時はTransportのスケジューリングに沿ってAudioSequenceのイベント実行

@sigprogramming
Copy link
Contributor Author

@romot-co
レビュー&動作をまとめていただきありがとうございます!

Phraseのクエリ(ハッシュ)が追加・変更されていたらクエリを(再)作成し合成

ここは、以下の流れになっています…!

  1. クエリが未生成の場合は生成(生成済みの場合は何もしない)
  2. Phraseのクエリが生成・変更されていたら音声を再合成し、phrase.queryHashを更新

また、拍子は音声の合成に関係しませんが、ひとまず拍子が変更された場合もレンダリング(再合成)が行われるようになっています…!

@sigprogramming
Copy link
Contributor Author

sigprogramming commented Apr 10, 2023

@Hiroshiba
なるほどです!その形にすると、phraseの持つscoreが「全体scoreの一部分のコピー」ではなくなるので一元化できて良さそうなのですが、レンダリング以外の処理でもphraseを意識する必要が出てきてしまうかもと思いました。
ユーザーも、phraseを意識して操作を行うことはないかも…?と思いました。

フレーズに分割するのは、分割してレンダリングするため(必要な部分だけレンダリングするため)で、
歌ボ形式ではひとまず1フレーズ分の楽譜情報から1フレーズ分の音声が合成される形ですが、
例えば、前後のフレーズを含めた3フレーズ分の楽譜情報を元に1フレーズ分の音声が合成される場合、
(各推論・音声合成で、入力の範囲 > 出力の範囲 となる場合)

  • 楽譜を休符で区切って得るフレーズ
  • レンダリングで最終的に合成される音声の範囲としてのフレーズ

この2つは意味的に異なるので、まとめて「フレーズ」として扱わない方が良いかも…?と思いました…!

@Hiroshiba
Copy link
Member

あーーーー なるほどです、ユーザーやプログラムにとって都合の良い区間単位と、エンジンにとって都合の良い単位(今はPhrase)が異なる場合があるということですよね。たしかにです!!

@Hiroshiba
Copy link
Member

approveが揃っていそうなのでマージさせて頂きます!!

@Hiroshiba Hiroshiba merged commit bef16f5 into VOICEVOX:project-s Apr 11, 2023
@sigprogramming sigprogramming deleted the utavo branch June 5, 2023 13:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants