VOICEVOX / voicevox_project

VOICEVOX内のプロジェクトを管理するリポジトリ
15 stars 3 forks source link

テキスト音声合成ライブラリの作成 #1

Closed Hiroshiba closed 1 year ago

Hiroshiba commented 2 years ago

テキスト音声合成ができる薄いライブラリを作る計画です。 今エンジンとコアに機能が分離しているので、これらをうまいことまとめる必要があります。

Hiroshiba commented 2 years ago

最初に要議論で、議論はこちらで行われています

Hiroshiba commented 2 years ago

このプロジェクトの内容を「C++ TTSライブラリ化」に軌道修正し、本格的に稼働したいです。

C++ TTSライブラリができると嬉しいことはこんな感じです。

どこまでできるようにすべきかですが、まず初手は単純なTTS(文字→音声)ができるライブラリにするのが良いかなと思います。

その次が迷っているのですが、AquesTalk記法を使用可能にするか、中間表現(エンジンのQuery)を修正できるレベルを目指したいです。

モーフィング機能・辞書編集機能などは一旦保留にして上記2つを注力すれば良いのかなと思っています。

qwerty2501 commented 2 years ago

どのように配布するつもりでいますか? DLLでしょうか? またAPIをどうする想定でいますか?(素朴な C APIを定義しFFIされる想定か、そうではないか)

別言語に組み込みやすくなる

これは各言語ごとにラッパーライブラリができて初めて成立すると思うので、C++製ライブラリ+ラッパーライブラリの作成を行う必要がありそうです。

Hiroshiba commented 2 years ago

配布は今のVOICEVOX COREと同じで、動的ライブラリの形式を考えています。 APIも今のコアと同じでexternすれば良いのかなと思っています。(こちらは単純にそれ以外の方法に関する知識がなく。。)

言語ごとにラッパーライブラリが作れると嬉しいですね!! あとあとエンジンでも活用できると嬉しいので、特にpythonラッパーは作るモチベーションがありそうです。 とりあえずこのissueはC++ライブラリの作成を目指したいです。 1つめのマイルストーンの単純なTTS(文字→音声)の実装ができてインターフェースが定まり次第、ラッパーを作り始めても良いかもですね!

qwerty2501 commented 2 years ago

そういえば以前コメントしたこれを検討しても良いかもしれませんがどうしましょうかね。 とりあえず何も使わずにC APIでの公開にしておきますか? C APIで動的ライブラリとして公開するならstruct情報を隠蔽して公開する必要がありそうですね。(この場合将来的にQueryの修正レベルのことをできるようにするなら、恐らくライブラリのユーザーにheapメモリ管理をさせなければならないでしょう。)

Hiroshiba commented 2 years ago

SWIGも面白そうです。本格的に多言語展開する際にぜひ検討したいですね! 初手はとりあえずC APIとして公開を目指しましょう。同時並行で調査しておくと良いかも?

struct情報に関しては・・・あ、今のエンジンみたくjsonでやり取りするというのはどうでしょう。 エラーメッセージのとこみたく、ライブラリ側でjson文字列のメモリを確保してアドレスを返し、その解放はライブラリ側が担当するとか・・・ いやこうすると並列で取得したときにおかしくなりますね・・・うーん、できればメモリ確保の手間は避けられるようにしたいですが、なにか良い方法ないですかね・・・

qwerty2501 commented 2 years ago

SWIGについて少し調べましたが、Swift/Objective-Cの出力がサポートされてないようなのでスマホターゲットを考慮するとiOSをどうするかSWIGを使うにしても考えなければならなそうです。

エラーメッセージのとこみたく、ライブラリ側でjson文字列のメモリを確保してアドレスを返し、その解放はライブラリ側が担当するとか・・・ いやこうすると並列で取得したときにおかしくなりますね・・・うーん、できればメモリ確保の手間は避けられるようにしたいですが、なにか良い方法ないですかね・・・

coreのエラーメッセージのやつですよね。あれはおっしゃってるとおり並列で取得したときにおかしくなるので絶対にやめたほうが良いと思います。 structのフィールドを絶対に変えないという確信がある場合はstructを公開してもよいかもですが、その場合でも文字列用に確保したheapメモリの管理をしなければならず、どのみち手間になります。手間というよりは危険というべきかもですが。

C APIでDLLを扱う場合は、ライブラリの構造体用に確保したメモリの管理操作はユーザー側で行うのが一般的ではあるので、これを避けたければwrapperを提供し、その内部でメモリの管理を行うアプローチが良いのではないでしょうか?

SWIGだと定義したclass/structを指定するとそのコードを各言語用に出力できるようなので、そのあたりのwrapper処理コードを自動で出力してくれそうです。

Hiroshiba commented 2 years ago

SWIG、面白そうなのですが、ライセンスがGPLなので厳しそうでした。。。

一旦SWIGから離れて、最適解を探せればと思いました。

いま問題なのは、「可変長のメモリサイズが必要なパラメータを返すのはどうすればいいか」だという認識です。 C APIで実装するなら、よくある方法だと

  1. メモリサイズを返す
  2. mallocしてもらう
  3. memcpyする

でしょうか。 ユーザー側もこちらの実装側もちょっと手間ですが、この方法以外思いつかないのであればこの方法しかないのかなと感じました。。

y-chan commented 2 years ago

C APIに関して、以前AquesTalk10のNode.jsラッパーを書いたことがあり、AquesTalkがwavを生成する際はメモリアドレスとメモリサイズを返す、を行っていました。 また、AquesTalkに関して言えばメモリの解放もAquesTalk側が担っていました(AquesTalk_FreeWave関数が存在します) なので、AquesTalk側でいったんメモリを確保してもらい、AquesTalk側で確保されたメモリのアドレスとサイズを元にmalloc/memcpyし、FreeWaveを実行していました。

https://github.com/y-chan/node-aquestalk10/blob/380581872273c341af23af230db38cbebfcdd804/aquestalk10.cc#L181-L188

他言語ラッパーを書く際に、Cの要素を他言語のものに変換する(上の例であればNapi::Buffer<uint8_t>::Copyなどの)必要が出てきそうだとも思いました。 なので、前に倣えにはなりますが、

  1. ライブラリ側で勝手にメモリを確保し、それらの情報(アドレス・サイズ)を返す
  2. それらをコピーして使ってもらう前提にする
  3. ライブラリ側にメモリ解放関数を用意しておき、コピーした(使った)後は解放してもらうようにする

が良いかなと思いました。

qwerty2501 commented 2 years ago

@Hiroshiba ライセンスGPLってSWIG自体ですよね?SWIGはライブラリではなくコード生成なので大丈夫な気がします(要調査) ただ一旦C APIでやるという点については私も賛成です。

ちょっと上手く伝わってなかったのですが、メモリ確保/開放はライブラリ側で関数用意するイメージですね。 ただCなので、開放はその関数を明示的にユーザーが呼び出してやるイメージです。 ユーザー側がメモリサイズを指定したり、標準ライブラリのmalloc/freeを呼び出す実装だと実装ミスが多くなりそうなので避けたほうが良さそうです。

この構造体実装を隠すテクニックは Opaqueポインタ とよばれてるので参考にしていただければ

この中で特に実装を隠さなければならないモチベーションが高いのは下記ですね

Person構造体に変化があるたびに、Person.hの呼び出し側で再コンパイル・再リンクが必要。 ビルドの時間が増加します。またライブラリ側と呼び出し側で異なる定義のPerson構造体を想定している場合、リンクすると危険なことが起こるはずです。

DLLで配布した場合、動的にリンクされるので、例えばライブラリがバージョンアップして構造体の実装が変わった時にDLLのみ更新した場合危険な状態になります。

Hiroshiba commented 2 years ago

では一旦C APIで・・・! なるほどです、ライブラリ側でmalloc/freeを呼べば簡単という発想がなかったのでちょっと勘違いしてしまいました。 y-chanさんも仰ってる通り、ライブラリ側でmalloc/freeするのが良いと思います!

構造体の件ですが、そもそもデータの型がコード(header.h)に、データの内容がバイナリにある状態を避けたいかもです。 仰るとおりバージョンがずれるリスクがあります。 ので、データの形と、データの中身どちらも返す機構にしたいです。 一番手っ取り早いのはエンジンみたいにjson stringを返す方法かなと思っていますが、どうでしょう。

qwerty2501 commented 2 years ago

あまり一般的なアプローチではないのでユーザーからは違和感は持たれそうです。open jtalkのlabel formatが文字列で返されましたが、あれと似たような違和感があるかと

そもそもデータの型がコード(header.h)データの内容がバイナリにある状態を避けたいかもです

Opaqueポインタを使えばこれは達成できると思いますが、json文字列にしたいモチベーションは何でしょうか? すみませんバイナリにある状態を避けたいということは、そもそもユーザーから見えるところ以外でも型定義をしたくないということでしょうか?隠れていれば問題ないと考えているのですが、モチベーションは何でしょうか?

データの形と、データの中身どちらも返す機構にしたいです

データの形とは何でしょう?

Hiroshiba commented 2 years ago

そもそもstructは避けたいモチベーションがある感じです。

Python用にCythonでCラッパーを書いて思ったのですが、structやvectorなどのCの独自言語仕様があるとコンパイルが面倒なんですよね・・・ ラッパーを書くための専用の記法や変換規則を習得する必要がありました(ユーザーのモチベーションをくじいてしまう)。 https://qiita.com/shunsukeaihara/items/7a31a3379936611cb9ee#cdef-struct%E3%81%97%E3%81%A1%E3%82%83%E3%81%86%E5%A0%B4%E5%90%88 なので、文字列をよしなに解釈するほうが楽かもです。 もちろんユーザーにとって一番嬉しいのは言語ごとにラッパーがあることですが、とりあえずC APIを作るなら一旦文字列を返す方がユーザーにとって楽かなと思います。

あとそういえば、返したい中間パラメータはAudioQueryなのですが、これは内部で可変長の値を持つのでそもそもstructは無理かもです。 (structの中でvectorなどを使わずに可変長の値持つのってできましたっけ・・・?)

Segu-g commented 2 years ago

もちろんユーザーにとって一番嬉しいのは言語ごとにラッパーがあることですが、とりあえずC APIを作るなら一旦文字列を返す方がユーザーにとって楽かなと思います。

ここで想定されているユーザーとはラッパー製作者の方でしょうか,それともライブラリの利用者の方でしょうか. 恐らく,言語にかなり熟達しないと一般の開発者はラッパーなしに動的ライブラリとの連携は難しいような気がします. 一方,,ラッパー製作者はjsonエンコードされたデータ文字列を渡されるよりstructを渡してもらった方がインターフェースを構築するのは楽な筈です. 無論,stringをそのまま渡すだけのライブラリなら簡単に作れるかもしれませんがそのために利用者にjsonのパースを求めるのは天秤が釣り合っていないように感じます.

structの中でvectorなどを使わずに可変長の値持つのってできましたっけ・・・?

C言語で可変長の値を持つには配列(mallocで確保するメモリへのポインタ) を用います. reallocやfree等がデストラクタとして実装できないので面倒ですがC APIなら仕方ないところがあると思います.

qwerty2501 commented 2 years ago

@Hiroshiba

Python用にCythonでCラッパーを書いて思ったのですが、structやvectorなどのCの独自言語仕様があるとコンパイルが面倒なんですよね・・・

これはOpaqueポインタで隠蔽すれば発生しない問題ですよね。

あとそういえば、返したい中間パラメータはAudioQueryなのですが、これは内部で可変長の値を持つのでそもそもstructは無理かもです。

これもOpaqueポインタで隠蔽すれば内部での実装はC++を使えるため、structフィールドとしてvectorを使用可能です。代わりにアクセサ用の関数を結構生やしてあげる必要はありますが mallocでもできますが、この文脈ではわざわざ危険を犯してまでvectorを使わずにmallocする必要はないと思います。

Hiroshiba commented 2 years ago

実際のユーザーの方(ライブラリの利用者)を想定していました! たしかにラッパーがないと利用はかなり難しそうです。

ので、たしかに(ラッパー製作者も含めた)C++ユーザーから使いやすい形が良さそうに感じました! structが良さそうです!

いろいろ議論していて、中間パラメータ(struct)周りは @qwerty2501 さんにおまかせできるとすごく嬉しいのかなと感じました!!!

の2つを @y-chan さんにお願いし、

を @qwerty2501 さんにお願いできればおそらく最高かと思ったのですが、どうでしょう・・・?👀

qwerty2501 commented 2 years ago

OKです。 私が担当するのは外部公開用の関数実装と、やりとりするための型定義でいいですかね? Query機能の実装も必要ですか?

y-chan commented 2 years ago

言語にかなり熟達しないと一般の開発者はラッパーなしに動的ライブラリとの連携は難しい

一方,,ラッパー製作者はjsonエンコードされたデータ文字列を渡されるよりstructを渡してもらった方がインターフェースを構築するのは楽

私もこれに同意です。C++ライブラリを誰でも使えることを目指すよりも、ラッパーライブラリを作りやすくする方が汎用性が高いはずなので、structを使うのがきれいに収まるかなと思いました。

  • 単純なTTS(文字→音声)の実装
  • AquesTalk記法の実装

問題ないです。現状のエンジンを元に、structの定義等を軽く作ってしまうと思うので、それをqwertyさんに中間表現出力用にいじってもらう形になるかなぁと思いました。

Segu-g commented 2 years ago

これもOpaqueポインタで隠蔽すれば内部での実装はC++を使えるため、structフィールドとしてvectorを使用可能です。代わりにアクセサ用の関数を結構生やしてあげる必要はありますが mallocでもできますが、この文脈ではわざわざ危険を犯してまでvectorを使わずにmallocする必要はないと思います。

実際にライブラリを開発していくにあたって恐らく外部からQueryの中身を操作したいという欲求が出てくるはずです. ABIを維持するTTSライブラリとABIが変わるSynthesisライブラリ等で分けることもできると思います. この時, 外部にデータを渡せるCの構造体とコンパイラなどに依存してしまうC++のクラスではかなり勝手が変わってしまうと思います. 前者では不透明参照を解決してあげるだけで外部からデータを見ることができますが,後者ではわざわざアクセサ関数を提供しないといけません.

その時になってから考えても良いとは思いますが,外部に出しうる構造体についてはクラスを使わずに構造体で書いた方が良い思うのですがどうでしょうか?

追記

ABIレベルで後方互換性を確保すべきかは分からないですが,アクセサ関数を提供する形はstructのlayoutに依存しないのでもしかしたら前方互換性が確保できるかも? ABIが変わってるのにロードできるのもそれはそれで怖そうですが・・・

Hiroshiba commented 2 years ago

@y-chan @qwerty2501 お二人ともありがとうございます!!

私が担当するのは外部公開用の関数実装と、やりとりするための型定義でいいですかね? Query機能の実装も必要ですか?

とりあえず外部公開用の関数実装と、やりとりの型定義をお願いしたいです! (そこまでいけばみんなコミットできそうなので)

全Query機能の実装は大変な一方、openjtalkが無くても音声合成できるだけの情報・機能があると嬉しそう・・・? ちょっとパラメータをリストアップしてみました。

```python class Mora: text: str = Field(title="文字") consonant: Optional[str] = Field(title="子音の音素") consonant_length: Optional[float] = Field(title="子音の音長") vowel: str = Field(title="母音の音素") vowel_length: float = Field(title="母音の音長") pitch: float = Field(title="音高") class AccentPhrase: moras: List[Mora] = Field(title="モーラのリスト") accent: int = Field(title="アクセント箇所") pause_mora: Optional[Mora] = Field(title="後ろに無音を付けるかどうか") is_interrogative: bool = Field(default=False, title="疑問系かどうか") class AudioQuery: accent_phrases: List[AccentPhrase] = Field(title="アクセント句のリスト") prePhonemeLength: float = Field(title="音声の前の無音時間") postPhonemeLength: float = Field(title="音声の後の無音時間") ```

is_interrogativeメンバーはありますが、擬似疑問文機能は初手で無くても良いかなと思っています。 (もちろん初手で実装してしまっても・・・!)

ちょっと量が多そうであれば、一旦pitch系とlength系は省いて、音素系とアクセントとpauseを実装とかもありかなと思いました!

qwerty2501 commented 2 years ago

フィールド結構あるので、その分関数が多くなっちゃうんですが大丈夫ですかね? 正直DLLでの互換性保ちつつ他のやり方知らないので代替案は出せませんが・・・

Hiroshiba commented 2 years ago

関数というのは外に出すAPIでしょうか 👀 APIの数は3個くらい増えるかなと思っていて、それくらいなら全然大丈夫だと思います。 内部の関数がいっぱい増えるのは仕方ないと思います・・・!

qwerty2501 commented 2 years ago

外に出すAPIですね。 structの実装を全て隠すので、アクセスさせたいフィールドがある場合はそのフィールドへのアクセサ関数を公開する必要があります。

例としては以下のような感じですね。関数名とかは結構適当です。

typedef struct VoicevoxMora_  VoicevoxMora;

char* voicevox_mora_get_text(VoicevoxMora* voicevox_mora);
void voicevox_mora_set_text(VoicevoxMora* voicevox_mora,char* text);
uint64_t voicevox_mora_get_pitch(VoicevoxMora* voicevox_mora);
void voicevox_mora_set_pitch(VoicevoxMora* voicevox_mora,uint64_t pitch);
...

こういう感じでユーザーにアクセスさせたいフィールド毎に関数を公開する必要があるので、フィールド数に比例して関数を公開する必要があり、結構な数になるかと

参考までに例えばgtkはそういう感じの関数定義になっています https://docs.gtk.org/gtk3/class.Button.html#methods

Segu-g commented 2 years ago

フィールド結構あるので、その分関数が多くなっちゃうんですが大丈夫ですかね? 正直DLLでの互換性保ちつつ他のやり方知らないので代替案は出せませんが・・・

一応standard-layoutなクラスであればプロパティの対応するinitial sequenceは等しくなるはずなので,

もしくは

という制約が入れば前方互換性は保てるとかがあります.

https://timsong-cpp.github.io/cppwp/n3337/class.mem#19

参考: https://en.cppreference.com/w/cpp/language/data_members#Standard_layout

ただgetter/setterを用意するのとどっちが簡単かといわれるとgetter/setterの方が簡単で理解しやすいと思います. 過去のABIに存在する関数でそれらのプロパティを使わないという制約は~~~ExみたいなAPIが生えてしまう温床にもなりそうですし,ライブラリ利用者側にインスタンスを作成させないという制約はそもそも禁止する方法がありません. 不透明型ではインスタンスの作成がライブラリ側のみで行えますが,インスタンスを作ることを縛れるのと縛れないのでは大きな違いですね・・・

qwerty2501 commented 2 years ago

そうですね。加えて上記案だと両方共ユーザーが間違えた使い方をしていても間違えていることに気づきにくいというのもあるのでOpaqueポインタを使用して安全性を高めたいというモチベーションも強いですね。 Opaqueポインタにすると生成と開放をライブラリ側で縛れるというのはかなり大きいメリットであると思います。

関数公開する量はかなり増えますが。

Segu-g commented 2 years ago

そもそも互換性をどこまで保証すべきかという部分もあると思います. 先にも言ったようにQueryの中身を隠蔽すればABIは互換性を保ちますし, TTSでも頻繁に変更したい設定は別のバージョン互換性のある部分として別の構造体や引数に入れるのもありだと思います.

動的ライブラリは差し替え可能なものですが同梱されることも多いものなので,個人的にはABIが変わったらバージョンも変えて良いんじゃないかなと思っていました.

そうですね。加えて上記案だと両方共ユーザーが間違えた使い方をしていても間違えていることに気づきにくいというのもあるのでOpaqueポインタを使用して安全性を高めたいというモチベーションも強いですね。

多分過去の関数で新しいプロパティを使わない方の案だとユーザーの使い方では壊れないはずです.もっとも製作する方はバグに気づけないので壊れやすくなると思います.

Hiroshiba commented 2 years ago

structの実装を全て隠すので、アクセスさせたいフィールドがある場合はそのフィールドへのアクセサ関数を公開する必要があります。

あー! なるほどです。

互換性ですが、できれば維持できると嬉しいです!

ちょっとお二人の方法が完璧に理解できていないのですが、プロパティを注意深く足していく方法とアクセサを大量に用意する方法は、どちらかというと後者のほうが扱いやすいのかなと思いました!(OSSに向いてそう) あまりにも関数が多くて使いづらいと感じた際は、ぜひ移行を考えたいです。

そういえば、パラメータは全てstructで持っておき、serializerとdeserializerを作って保存・読み出し関数を用意する手もあるのかなと思いました。 用意するのはこの3つくらいなので、APIの見た目的に綺麗なのが良さそうに感じたのですがどうでしょう・・・?👀 (シリアライズ先は適当にjsonでもバイナリでもいいと思いますが、文字列だとデバッグしやすいので個人的に好きです)

struct AudioQuery;
AudioQuery deserialize_param(char* obj);
char* serialize_param(AudioQuery param)
qwerty2501 commented 2 years ago

ちょっとなぜserializerとdeserializerを作る必要があるのか理解できてないのですが、もう少し説明もらえますか? どういう使われ方を想定しているか、Pros Consなど

Hiroshiba commented 2 years ago

そもそも互換性が必要なのは、パラメータを保存することを想定しているからだと思っていて、 であればその保存とロード時に互換性をもたせれば全部うまいこと行きそうだなと思いました。

Prosはsetter/getterを大量に用意しなくていい・互換性を気にしてstructの順番を決めなくて良い感じです。 Consは特に思いついてないです。

Segu-g commented 2 years ago

そもそも互換性が必要なのは、パラメータを保存することを想定しているからだと思っていて

私はバイナリ互換性についての議論だと思っていました. 参考までにABI (Application Binary Interface)互換性とは例えばライブラリAのバージョンがv1.0からv1.1に変わった時にv1.0を利用していたプログラムの変更や再コンパイルなしに共有ライブラリを差し替えるだけでそのまま動き続けることができる互換性を指します.

@qwerty2501 さんの提案されているOpaque pointerは実際の構造体の中身を隠蔽し,Queryのインスタンスへの編集にはgetter/setter関数を提供することでtypescriptのInterfaceのようにユーザーにプロパティへの存在とそれらへのアクセス手段を全バージョンにて保証することができます. この際, 実際にOpaque pointerが保持しているstructがどのようなプロパティを持っているかはABIのバージョンが変わるたびに変化しますが, 不透明型なので生成, 管理は全てライブラリ側で行うこととなり, それをユーザーが気にすることはありません. ただしユーザーはqueryを構造体という形で見ることができず, 全てアクセサ経由で操作する必要があります. (もしくはそのバージョンの構造体への相互変換関数を毎バージョンに生やすとか?)

私の提案したstandard-layoutという方法はC++においてC言語の構造体に変換可能なObjectが満たす POD (Plain Old Data) という構造がstandard-layoutかつtrivial typeであるという制約から, Query構造体を前のバージョンの継承関係となるように末尾にプロパティを実装するという試みです. すなわちv1.0のQueryをQuery1, v1.1のQueryをQuery2とし, Query2はQuery1を継承するようになります. このとき, v1.0のfuncはQuery1*で呼び出すことができますが, Query2はQuery1を継承しているのでQuery2でも呼び出すことができます. 従ってこの方式で前方互換を確保するには

といった感じで@qwerty2501 さんの提案された方式よりかはかなりデメリットが目立つので自分でもあまりおススメしません.

@Hiroshiba さんの提案されているserialize_param, deserialize_paramはQueryとしてJSONなどのDictionary Likeな中間表現を用いることでこれらの差異を吸収しようといった感じでしょうか? JSONなどの文字列型はCではパース, エラーハンドリング, 変更などが手間になりますがマイグレーションなどを気にしなければうまくいくかもしれません.

どの方法を用いるにしてもQueryの内容を変更可能にしなければABI互換性を保つことができます. すなわち, TTSのパラメータについてだけQuery外に持っていきQuery自体は不透明型にすることで, 詳細なデータ構造は使いたいユーザーだけが解決してもらうという形もアリです. 無論Query外のパラメータを互換性を保ったまま変更は出来なくなるのでその辺りはしっかりと考える必要があります. また, Query内を変更したいというときは上記のいずれかの方法か,もしくはまだ知らない別の方法を用いる必要があるでしょう.

纏めると, Queryの編集をユーザーに提供する際に

といった感じでしょうか

個人的には全アクセサをgetter/setterにするとstructが直観的に見れないので, そのバージョンの構造体への相互変換関数を毎バージョンに生やす形で@qwerty2501 さんの方法がいいのかなと思いました.

qwerty2501 commented 2 years ago

@Hiroshiba おおむね Segu-g さんが説明されてる通りABI互換性の話についての議論です。 ABI互換性についての議論のため、パラメータの保存有無にかかわらずこの問題は発生します。 ちょっと話が噛み合っていないようにも感じてますが、上手く伝わってますか?

Hiroshiba commented 2 years ago

すみません、よくわかりました!!僕が気にしていたのはデータの互換性の話でした。

ABI互換性についてしっかり知ってきました。 個人的には、最初はABIレベルの互換性がある状態で開発しない方が良いのかなと思いました。 理由は単純に、まだβ版で色々と実験しつつ進めるときに変更が大変そうなのと、そもそもABI互換性があると嬉しい人が現状ほぼいなさそうなためです。 一言でいうと、難しくて大変そうだけどユーザーがいなそうだなーと。。 実はそんなメンテナンス含めて開発は難しくなかったり、ラッパーの開発が楽になったりするのであれば最初から実装しても良さそうに感じました。

のちのちはABIを気にして作るのも良いのかなと感じました。 @Segu-g さんの仰っていたことに近いかもですが、ABI互換性を気にしないライブラリと、それをラップしてABI互換性を気にしたライブラリがあっても良いのかなと思いました。 多言語でVOICEVOXを使える薄いラッパーを開発する人はたぶんリビルドに抵抗がないので後者を使うし、直接C++アプリを作る人は前者を使いそうです。

(ただまあ、直接C++アプリを作る人よりラッパーを使う人のほうが多そうに感じているので、ラッパー作成に注力した方が喜ばれそうな気がしています)

qwerty2501 commented 2 years ago

了解です。 となるとほぼstatic link libraryとほぼ同じ感覚で作れるはずなのでC API部分の実装は私じゃなくても大丈夫そうですね。(私担当のままでも大丈夫ですが)

直接C++アプリを作る人は前者を使いそうです。

C++で使う場合はソースコードごと一緒にコンパイルすると思うので特に気にしなくて大丈夫そうです。

ラッパーについてはたしかにそうですね。今どきC APIだけ提供してあっても誰も使わない可能性はありそうですしラッパー作成に注力したほうが良さそうです。というかC APIの提供いらないかもですね。 方針変えてまずはC APIを作らずに単純にC++のライブラリとして提供してある程度軌道に乗ったら各言語向けのラッパー作成にとりかかったほうがよいかもとちょっと思いました。

Hiroshiba commented 2 years ago

正直なところ、C++でどういうAPIを作ったほうが良いか(作らなくても良いか)がわからずにいます。 そこのあたり詳しい方にお願いできると良いものができると思うので、ぜひお願いしたいです。

今どきC APIだけ提供してあっても誰も使わない可能性はありそうですし

(これもよく知らないのですが)別言語でラッパーを作るとき、C APIを動的ライブラリにビルドしたものをラップするのが楽なのかなと想像しています。 であれば一旦C APIは作ったほうが良いのかもと思いました!

qwerty2501 commented 2 years ago

別言語でラッパーを作るとき、C APIを動的ライブラリにビルドしたものをラップするのが楽なのかなと想像しています。

そうですね。ラッパーを手動で作成する場合はC APIが必要になってくると思います。 ただSWIGについて現状保留状態なのでもしSWIG使う場合になったらC++のclassを対象として出力できそうなのでその場合はC APIを作る必要がない可能性が出てきます。i OS向けには手動で作る必要がありそうですが。 またC APIが必要になったとしても後から作るということはできますし、第一段階としてはなしでもいいかなとちょっと感じたといったところですね。

またC APIについてですが、structの実装を公開しても文字列や配列についてはheapを確保してるのでこれらを開放する必要があります。開放用の関数を定義してもユーザーが気付けるかどうかはちょっと怪しいかもしれません。この場合ドキュメントなどで周知するぐらいしかなさそうですが・・・

shigobu commented 2 years ago

C#使いとしては、C APIをラップする方が簡単です。

Hiroshiba commented 2 years ago

YMM4にCoeFontが初期搭載されるニュースが発表されました。 VOICEVOXが最初に到達できる未来だったかもしれず、正直とても悔しいです。 (思いつけず、不甲斐なくて本当に申し訳ないです・・・。)

VOICEVOXも同じこと(TTSシステムの同梱)が可能になる状態を、なるべく早く実現したいなと感じました。 そのための手がこのC++ライブラリ作成プロジェクトだと思います。

身勝手なのですが、ロードマップ(というか優先順位)を引き直せればと思います🙇 「Windows上でVOICEVOX.exeをインストールすることなくTTSが可能」 という状態を最速で目指すロードマップです。

高優先

オプション

更にオプション


僕たちの今の立ち位置から、僕たちのスキルを合わせれば、おそらく2週間でリリースまで到達できると思います。 その立ち位置にいるのは間違いなくみんな(特に @y-chan さん)のおかげです。

コア開発に携わってくださったVOICEVOXチームの優秀なメンバー(敬称略 @Yosshi999 @Oyaki122 @y-chan @aoirint @PickledChair @qwerty2501 @Segu-g @shigobu )の皆様、もしよければお力をお借りしたいです。 どうか、どうかよろしくおねがいします・・・・・・!!🙇

Hiroshiba commented 2 years ago

(Discordに書きたかったのですが今不調っぽいのでこちらで。。) 初手はおそらく @y-chan さんの、TTSを可能にするプルリクエストが最速かなと感じています。 マージしてしまえばあとはパラレルで動けるので、なるべく早くマージできるとすごく嬉しいです(急かしてしまって申し訳ないです。。) お手数おかけしてしまうのですが、もしよければ何卒よろしくお願いいたします🙇

Hiroshiba commented 2 years ago

すごく昔に作ったLite版モデルで音声合成してみました。

↓通常版

https://user-images.githubusercontent.com/4987327/157507761-2331809e-c446-4527-9f60-16f6f308d9df.mp4

↓Lite版

https://user-images.githubusercontent.com/4987327/157507771-747eb47c-d66f-4632-bba3-8038fedb5a86.mp4

onnxモデルのサイズは1/10程度に、生成速度は爆速になりますが、やっぱり品質は明確に落ちそうな印象です。 Lite版作成は実験としては進めますが、優先度はあまり高くせず進めようと思います。

qwerty2501 commented 2 years ago

あまり状況を把握していないのですが、YMMってすでにvoicevox APIに対応してませんでしたっけ。 今あるengineを同梱する方向だとよくないんでしょうか

Hiroshiba commented 2 years ago

ファイル容量がざっと300MBくらい違ったりします。 仮にVOICEVOXの軽量版ができれば(辞書を抜けば)50MBくらいになるので結構差がありそうです。 サーバーを起動するexeとdllだと取り回しやすさがだいぶ違うと思うので、なんにせよ早めに作りたいところです。

Hiroshiba commented 2 years ago

@qwerty2501 あ、issueの内容とちょっと異なるフランクな話題はVOICEVOXのユーザーコミュニティdiscordサーバーを間借りして話していたりします。 かなり気軽にいろんな話ができるので、よかったらぜひ! https://twitter.com/hk_coil424/status/1432351677026160641

Hiroshiba commented 2 years ago

@y-chan さんのTTSできるコードがマージされました! (本当にありがとうございます!!!) https://github.com/VOICEVOX/voicevox_core/tree/cpp-library

どんどん機能実装&使い勝手を良くしていきたいので、もしよければプルリクエストお待ちしています! (ブランチがmainではないのでご注意ください)

Hiroshiba commented 2 years ago

オプション側のコード追加もじゃんじゃんやっていけると嬉しいですね・・・!

Hiroshiba commented 2 years ago

製品版をビルドしてみました。TTSが動きました!

残りやりたいことはまだまだありますが、一段落だと思います。 C++のサンプルコード(example/cpp)実装や、テストの実装などが次の課題だと思います。 じゃんじゃん進めていけるととても嬉しいです。 https://github.com/VOICEVOX/voicevox_project/issues/1#issuecomment-1062100395

Hiroshiba commented 2 years ago

こちら、別言語で気軽に使えるようにしたり、いろんなサービスで気軽に利用できるようにしたりできると世界が変わってくると思うので、どんどん進めて行きたいかもです。

ここのリストのタスクを整理すると、これらがメインで残っていそうでした。

オプションとしてはこれらがありそうです。

もしよかったらぜひ・・・!

shigobu commented 2 years ago

Windowsで動くC++用のサンプルコード、Visual Studio(VSCodeで無い)のプロジェクトで良いなら、私作ります。

Hiroshiba commented 2 years ago

おお!ぜひお願いします!!

Hiroshiba commented 2 years ago

@shigobu さん、Windows用のサンプルありがとうございます!! @Oyaki122 さんも、埋め込みありがとうございます!!

もう動的TTSライブラリのリリースができる気がします! ただテストコードがまだだったり、linux用のサンプルコードがなかったりなのが少し不安ポイントでもあります。 引き続き、このあたりの実装を募集しています。

よかったらぜひ!!

PickledChair commented 2 years ago
  • Mac・Linuxでも動くようにする

今日 Mac・Linux 向けの小さなサンプルコードを書いてみていました(コマンドの引数で与えられたセリフから音声を生成し、audio.wav を生成する単純なものです)。問題なく動くようです。あとは CMake でビルドできるようにしたいと思って作業しています。