Open Yosshi999 opened 1 day ago
決めなきゃいけないこと
struct Intermediate
に何を詰めるべきか(workaround paddingの長さ、音声長など)名前については、ユーザー向けレベルならstruct Intermediate
はstruct Audio
とかでもよいと思います。というのも"AudioQuery"から作るので。
またintermediateがSynthesizer
の参照を保持して、メソッド.render()
を生やしてもいいんじゃないかと。
(内部実装レベルではちょっと苦労しそうですが)
struct Intermediate
をstruct Audio
とした場合、synthesis_intermediate
の方は…思い付かない。seekable_synthesis
とか? (単なる"streaming"というより、任意区間に対して実行できることからRustのSeek
のような感じで)
audio: Audio = synth.seekable_synthesis(aq, amaama_zundamon)
wav_part: bytes = audio.render(slice(t1, t2 + 1))
wav_whole: bytes = audio.render(slice(None))
# よい感じに分割したイテレータを作る
for wav_part in audio.render_segments():
...
intermediateがSynthesizerの参照を保持して、メソッド.render()を生やし
https://github.com/VOICEVOX/voicevox_core/pull/854 で組んでみましたがpyo3がまだできていません.. Audioに面倒なライフタイムなどが付いてきているんですがこのままpythonに渡してしまっても大丈夫なんでしょうか
error: #[pyclass] cannot have lifetime parameters. For an explanation, see https://pyo3.rs/latest/class.html#no-lifetime-parameters
--> crates\voicevox_core_python_api\src\lib.rs:429:29
|
429 | pub(crate) struct Audio<'synth> {
|
Audioの中にsynthesizerの参照を持たせるためにはlifetimeを導入する必要があるが、pythonにはこのままでは渡せない
とりあえずrender関数をSynthesizerの中に入れました
とりあえず複雑性を避けた方が議論が円滑になりそうですし、今はそれでよいと思います。
もしやるとしたら、Arc
を使ってSynthesizer
(#[pyclass]
の方)をClone
にし、
#[pyclass]
+ #[derive(Clone)]
pub(crate) struct Synthesizer {
- synthesizer: Closable<
- voicevox_core::blocking::Synthesizer<voicevox_core::blocking::OpenJtalk>,
- Self,
- SingleTasked,
+ synthesizer: Arc<
+ Closable<
+ voicevox_core::blocking::Synthesizer<voicevox_core::blocking::OpenJtalk>,
+ Self,
+ SingleTasked,
+ >,
>,
}
ouroborosでライフタイムパラメータを消す、という形になるかと思います。
use ouroboros::self_referencing;
#[pyclass]
#[self_referencing]
struct Audio {
synthesizer: Synthesizer, // `#[pyclass]`の方
#[borrows(synthesizer)]
#[not_covariant]
inner: voicevox_core::blocking::Audio<'this>, // Open JTalkは要らないので、`&voicevox_core::blocking::Synthesizer`自体ではなく`&Status`を持つようにすれば型引数の`O`は消えるはず
}
(ただ #832 の考えかたからすると"pyclass"丸ごとじゃなくてClosable
の中身のRwLock
のガードを持つようにすれば一貫性を保てるのですが、そのためにはSend
という性質をどうにかして得る必要があって、今このリポジトリで使っているblockingというライブラリのスレッドプールを拝借してそこからasync-channelで…ということになりそう)
[追記] 文脈として、class Synthesizer
には__enter__
と__exit__
があります。
今のところヒホさん抜きで話が進んでいますが、COREのRust API & Python APIを実装しながら議論する、という今の流れはいいんじゃないかなと思っていることを表明しておきます。
余程複雑なAPIにしない限りはC APIも(ENGINEの)Web APIも後から考えられるはず。 (先日のDiscordでの議論を踏まえても、精々デストラクトがどうのIDがどうのという話に帰着できそう)
指定区間の単位(秒数か24kHz or 24k/256Hzで表現されるフレーム数か)
フレーム数が良いと思います! ソングのときに考えたのですが、秒にすると丸め込み方向とかも考える必要があって、全部フレーム単位にしたほうが良いな~となりました。
struct Intermediateに何を詰めるべきか(workaround paddingの長さ、音声長など)
パッと正解が思いつかないですね。。試行錯誤して作っていく感じになるかも。 必要であればガッツリ考えます!!
パディング、つまり音声を出力するために前後何フレーム余分に中間表現を入力する必要があるかですが、これはAPIには露出させない形を目指せると嬉しそうです。 (つまり必要なパディングの計算などはコアの中で勝手にやってくれる形)
名前については、ユーザー向けレベルならstruct Intermediateはstruct Audioとかでもよいと思います。というのも"AudioQuery"から作るので。
返り値をクラスインスタンスにするの良いですね!! どっちかというと最終成果物がAudioなので、AudioRendererとかRendererもわかりやすいかもですね!
今のところヒホさん抜きで話が進んでいますが、COREのRust API & Python APIを実装しながら議論する、という今の流れはいいんじゃないかなと思っていることを表明しておきます。
良いと思います!!
フレーム数単位にするならSynthesisIntermediateに実際のフレームレートを持たせた方がいいと思います、というのも記憶が正しければtts経由だとサンプリングレート取れないはずなので。(あとラッパー作るのも楽になる)
フレーム数単位にするならSynthesisIntermediateに実際のフレームレートを持たせた方がいいと思います
これちょっと弱点があって、関数を実行するまでフレームレートがわからないんですよね。 特にUI作ってると先にフレームレートが知りたくなることもあり。
個人的には別のとこに置くのが良いんじゃないかな~~~と思ってます。 VOICEVOX ENGINEリポジトリは、EngineManifestというのを別途作ってframeRateを置いてます。
24kHzから変える予定が無いのなら、グローバルな値として提示してもいいんじゃないかと思います。C APIなら#define
でもよさそう。
(次点は…VVM単位?)
名前については、DEFAULT_SAMPLING_RATE
じゃなくてBASE_
とかにするか、単なるSAMPLING_RATE
にしてもいいかもしれません。frameRate
とも統一性が出るんじゃないかと。
たしかに、サンプリングレートもフレームレートも一旦固定で良いと思います! 将来的には変わる可能性はあるだろうけど、もしあるとしてもほんとに先になりそう。
内容
音声を逐次的に生成する機能を提供し、長い文章の音声を生成する際のレイテンシの短縮をめざす。
関連
Pros 良くなる点
Cons 悪くなる点
実現方法
現在の音声合成処理(synthesis)はaudio queryを入力としてスペクトログラム生成 -> 音声波形生成の順に処理されており、前半と後半の処理時間の比は大体1:10くらいである。また、スペクトログラム生成は内部のモデルの仕様上audio query全体を入力する必要があるが、後半の音声波形生成は十分にマージンを取っていれば任意の位置から任意の長さの音声を生成することができる。
参考: https://github.com/Yosshi999/streaming_hifigan
そこで、現在
decode
として処理されている関数を二つに分け、generate_full_intermediate
関数によってえられる中間表現(現状ではスペクトログラム)をいったんユーザーに返し、その中間表現と指定区間を入力として音声を生成するrender_audio_segment
関数によって最終的な音声を生成する。実装が必要なもの
struct Intermediate
: ユーザーにいったん返す中間表現。実態(スペクトログラム)には触れてほしくないが、lengthは後段の生成区間指定などのために参照可能にする必要がある。blocking::Synthesizer::synthesis_intermediate(&self, &AudioQuery, StyleId, &SynthesisOptions) -> Result<Intermediate>
: 現在のsynthesis()
の途中(decodeの前半部)までを実行する。現在音声が途切れる問題のworkaroundとして前後にパディングがついており、これを音声生成時に取り除くためパディング長をどこかで共有する必要がある。blocking::Synthesizer::render_audio_segment(&self, Intermediate, usize, usize) -> Result<Vec<u8>>
: 中間表現から指定した区間の音声を生成する。nonblocking::*
も必要?あるとうれしいもの