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

最適な話者ピッチ範囲を送れるように #1121

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions run.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
Speaker,
SpeakerInfo,
StyleIdNotFoundError,
StylePitchRange,
SupportedDevicesInfo,
UserDictWord,
VvlibManifest,
Expand Down Expand Up @@ -537,6 +538,21 @@ def multi_synthesis(
background=BackgroundTask(delete_file, f.name),
)

@app.post(
"/optimal_pitch",
response_model=StylePitchRange,
tags=["その他"],
summary="指定したスタイルに対して最適なピッチ範囲を得る",
)
def optimal_pitch(style_id: StyleId) -> StylePitchRange:
Copy link
Member

Choose a reason for hiding this comment

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

APIとデータ構造について

ピッチ範囲を取得できる/optimal_pitchAPIを実装していますが、これはAPIを足すのではなく、スタイル関連情報がまとまっているところに足す形だとより扱いやすいかもしれません。
例えばSpeakerStyle内などです。
ただそれをするにはコードを読んでみた感じかなり改革が必要そうでした。でも良いAPI設計のためにやるべきだと感じています。
改革はプルリクエスト範囲から外れているので興味なければ苦痛かもしれません。もし興味あれば以下の議論に参加して頂ければ。


@y-chan さんと @tarepan さんに相談です。
たぶんピッチ範囲を格納するのは、StyleType等が保存されてるSpeakerStyle内が最適だと思うがどうでしょうか。
ただ、もし仮にSpeakerStyleにピッチ範囲を含める場合は問題があります。
SpeakerStyleCoreSpeaker内にあるのですが、CoreSpeakerはコアの読み込みから利用されているため、変更できなくなっています。
そもそもAPIと無関係であるべきなCoreSpeakerモデルがここに定義されてることが変で、コア用のデータ構造はコア読み込みの部分だけで閉じているべきに感じました。
なのでmetas/Metas.py内からCoreSpeakerの定義をやめ、Speaker直下に.name.speaker_uuid.supported_featuresを定義するのとかどうでしょうか。
たぶんAPIの返り値は一切変えずにCoreSpeaker定義を失くせると思っています。

この健全化作業を @tarepan さんにお任せしても良いでしょうか🙇
その変更のあと、 @Patchethium さんがこのプルリクエスト内で今のSpeakerStyle内に.pitch_rangeを追加するというのはどうでしょうか。

english

About the API and data structure

We are implementing an /optimal_pitch API that can retrieve the pitch range, but instead of adding this as a separate API, it might be more manageable to add it to a place where style-related information is aggregated, such as inside SpeakerStyle.
However, from what I've seen reading the code, this might require quite a bit of reform. But I feel it's worth doing for good API design.
This reform might be outside the scope of a pull request, so it could be a pain if you're not interested. But if you are interested, I would appreciate your participation in the following discussion.


@y-chan and @tarepan
Perhaps the best place to store the pitch range would be inside SpeakerStyle, where StyleType, etc., are stored, but there are issues with that.
SpeakerStyle is inside CoreSpeaker, but CoreSpeaker is being used from the core load, making it unchangeable.
It's odd that the CoreSpeaker model, which should be unrelated to the API, is defined here. I feel that the data structure for the core should be confined to just the core loading part.
So, how about stopping the definition of CoreSpeaker inside metas/Metas.py and defining .name, .speaker_uuid, .supported_features, etc., directly under Speaker?
I believe we can remove the definition of CoreSpeaker without changing any API responses at all.

Could we leave this rationalization task to @tarepan 🙇
After that change, how about @Patchethium adding .pitch_range to the current SpeakerStyle within this pull request?

Copy link
Contributor

@tarepan tarepan Apr 4, 2024

Choose a reason for hiding this comment

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

ピッチ範囲を格納するのは ... SpeakerStyle 内が最適

👍️
同意です。
意味論からすると SpeakerStyle 内に配置するのが妥当と考えます。

APIと無関係であるべきなCoreSpeakerモデルがここに定義されてることが変で、コア用のデータ構造はコア読み込みの部分だけで閉じているべき

👍️
同意です。
現状は、list[Speaker] が VV API に露出する型であり、CoreSpeaker が Core API を包む型となっています。
Speaker の定義時に CoreSpeaker 継承を用いた結果 VV API と Core API が相互依存化し、CoreAPI が不変だからピッチ情報追加 VV API 変更ができない、という状況に陥っています。

CoreSpeakerの定義をやめ、Speaker直下に.nameや ... を定義

👍️
同意です。
class Speaker(CoreSpeaker, EngineSpeaker): による VV API - Core API 相互依存がこれで解けます。

健全化作業を @ tarepan さんにお任せしても良いでしょうか

👍️
承りました。複数 steps / PRs で構成されるリファクタリングをおこないます。
影響範囲が複数ファイルにまたがるため、いま健全化すると既存 PRs とコンフリクト祭りします。他 PRs が review & merge され着手可能になった段階で、@Hiroshiba さんから @ tarepan へ改めてメンションをお願いします。着手可能メンションを受け次第、実装に着手します。

Copy link
Contributor Author

Choose a reason for hiding this comment

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

私はコアに関する知識が一切ないのでなんとも言えないですが、スタイル情報を持っているコア側から取るのは一番自然なのは同意です。もし @tarepan さん)が実装してくればこれで一番良いと思います。
それでは一応ここをcloseにして、コア側の健全化作業を待つことにします。

Copy link
Member

@Hiroshiba Hiroshiba Apr 30, 2024

Choose a reason for hiding this comment

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

遅くなりました!! 🙇

@tarepan ありがとうございます!!

他 PRs が review & merge され着手可能になった段階で、@Hiroshiba さんから @ tarepan へ改めてメンションをお願いします。

承知しました!と言いたいところなのですが、ちょっとどのタイミングならいけやすいかPR全部把握しているわけではないのでわからないというのが正直なところです。。 🙇
今ぱっとPRのタイトルを見て回った感じ、API Routerの変更とテストの追加が主だと感じました。
このタスクはWebAPI周り・コア周りのSpeakerデータ構造のタスクなので、意外と今衝突していないかもしれません。

@Patchethium さんもありがとうございます!!

Copy link
Contributor

Choose a reason for hiding this comment

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

@Patchethium
お待たせしました。#1202 がマージされ、エンジンのデータ構造が整理されました。
ピッチ範囲をエンジンの metas.json に配置するのであれば実装可能かと思います(コアの metas.json に置くかどうかはメンテナ判断かと思います)。

try:
low, high = metas_store.get_style_optimal_pitch_range(style_id)
return StylePitchRange(low=low, high=high)
except StyleIdNotFoundError as e:
raise HTTPException(
status_code=404, detail=f"該当するスタイル(style_id={e.style_id})が見つかりません"
)

@app.post(
"/morphable_targets",
response_model=list[dict[str, MorphableTargetInfo]],
Expand Down
11 changes: 9 additions & 2 deletions speaker_info/35b2c544-660e-401e-b503-0e14c635303a/metas.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
{
"supported_features": { "permitted_synthesis_morphing": "NOTHING" }
}
"supported_features": {},
"range": [
{
"style_id": 8,
"low": 5.07,
"high": 6.5
}
]
}
Comment on lines 1 to +10
Copy link
Member

Choose a reason for hiding this comment

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

データを配置する場所について

これはエンジンのmetas.jsonに書いてるけど、制作の都合上コアのmetas.jsonに書く方がやりやすいかもしれません。
データセットからピッチ範囲を計算する方針の場合は特に。
あとコアに書けばコアライブラリを使っている人も使えて便利かもしれません。
どうするかわからないけど、将来的にそういう設計に変えるかも、という共有です。

english

About where to place the data

This is written in the engine's metas.json, but it might be easier to handle if written in the core's metas.json, especially if we're calculating the pitch range from the dataset.
Also, if it's written in the core, it might be useful for people using the core library.
I'm not sure how to go about it, but it's a comment that we might change to such a design in the future.

Copy link
Contributor

@tarepan tarepan Apr 4, 2024

Choose a reason for hiding this comment

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

同意です。ENGINE の metas.json ではなく CORE から取得すべきだと考えます。
なぜならピッチ範囲情報と並列関係にある SpeakerStyle の属性値(name / id / type)が CORE から取得する情報だからです。

18 changes: 16 additions & 2 deletions speaker_info/388f246b-8c41-4ac1-8e2d-5d79f3ff56d9/metas.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
{
"supported_features": { "permitted_synthesis_morphing": "SELF_ONLY" }
}
"supported_features": {
"permitted_synthesis_morphing": "SELF_ONLY"
},
"range": [
{
"style_id": 1,
"low": 5.38,
"high": 6.44
},
{
"style_id": 3,
"low": 5.11,
"high": 6.5
}
]
}
18 changes: 17 additions & 1 deletion speaker_info/7ffcb7ce-00ec-4bdc-82cd-45a8889e43ff/metas.json
Original file line number Diff line number Diff line change
@@ -1 +1,17 @@
{}
{
"supported_features": {
"permitted_synthesis_morphing": "SELF_ONLY"
},
"range": [
{
"style_id": 2,
"low": 5.16,
"high": 6.2
},
{
"style_id": 0,
"low": 5.21,
"high": 6.13
}
]
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{
"supported_features": { "permitted_synthesis_morphing": "ALL" }
}
"supported_features": {},
"range": []
}
10 changes: 10 additions & 0 deletions voicevox_engine/metas/Metas.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
# NOTE: 循環importを防ぐためにとりあえずここに書いている
# FIXME: 他のmodelに依存せず、全modelから参照できる場所に配置する
StyleId = NewType("StyleId", int)
PitchRange = NewType("PitchRange", float)
StyleType = Literal["talk", "singing_teacher", "frame_decode", "sing"]


Expand All @@ -28,6 +29,12 @@ class SpeakerStyle(BaseModel):
)


class SpeakerOptimalPitchRangeItem(BaseModel):
style_id: StyleId
high: PitchRange
low: PitchRange


class SpeakerSupportPermittedSynthesisMorphing(str, Enum):
ALL = "ALL" # 全て許可
SELF_ONLY = "SELF_ONLY" # 同じ話者内でのみ許可
Expand Down Expand Up @@ -67,6 +74,9 @@ class EngineSpeaker(BaseModel):
supported_features: SpeakerSupportedFeatures = Field(
title="話者の対応機能", default_factory=SpeakerSupportedFeatures
)
range: List[SpeakerOptimalPitchRangeItem] = Field(
title="話者の最適ピッチ範囲", default_factory=list[SpeakerOptimalPitchRangeItem]
)


class Speaker(CoreSpeaker, EngineSpeaker):
Expand Down
9 changes: 9 additions & 0 deletions voicevox_engine/metas/MetasStore.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
StyleId,
StyleType,
)
from voicevox_engine.model import StyleIdNotFoundError

if TYPE_CHECKING:
from voicevox_engine.core.core_adapter import CoreAdapter
Expand Down Expand Up @@ -61,6 +62,14 @@ def load_combined_metas(self, core: "CoreAdapter") -> List[Speaker]:
for speaker_meta in core_metas
]

def get_style_optimal_pitch_range(self, style_id: StyleId) -> tuple[float, float]:
for meta in self._loaded_metas.values():
for range in meta.range:
if range.style_id == style_id:
return (range.low, range.high)
else:
raise StyleIdNotFoundError(style_id=style_id)


def construct_lookup(
speakers: List[Speaker],
Expand Down
5 changes: 5 additions & 0 deletions voicevox_engine/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,11 @@ class MorphableTargetInfo(BaseModel):
# reason: Optional[str] = Field(title="is_morphableがfalseである場合、その理由")


class StylePitchRange(BaseModel):
low: float
high: float


class StyleIdNotFoundError(LookupError):
def __init__(self, style_id: int, *args: object, **kywrds: object) -> None:
self.style_id = style_id
Expand Down
Loading