Skip to content

Commit

Permalink
複数エンジン対応: すべてのエンジンからキャラクター情報を取得する (#883)
Browse files Browse the repository at this point in the history
* add runEngineAll

* fix log

* fix log

* add killEngineAll

* fix log

* fix log

* add restartEngineAll

* call killEngineAll

* fix log unit

* npm run fmt

* add START_WAITING_ENGINE_ALL, RESTART_ENGINE_ALL

* npm run fmt

* DETECTED_ENGINE_ERROR engineKey

* npm run fmt

* fix check all ready logic

* fix engineState

* fix test

* type with undefined

* fix allEngineState logic

* fix test

* add comment

* multiple engine: SET_CHARACTER_INFOS, USER_ORDERED_CHARACTER_INFOS

* multiple engine: LOAD_CHARACTER_ALL

* fix buildFileName logic

* fix default style id

* fix characterInfos from vue

* pass engineInfos to vue

* fix log

* fix changeStyleId

* fix synthesis to use engineKey/Id

* fix character portrait to use engineId

* fix audiocell icon to use engineId

* fix character voice testplay

* wip fix voicesample

* fix test

* use <0.12 for project migration

* use semver for migration

* assume to be killed

* add memo comment

* fix comment

* call onAllKilled after final process kill errored

* fix log

* fix log

* fix infinite loop when using external engine

* add logs

* call IS_ENGINE_READY from IS_ALL_ENGINE_READY

* fix comment

* call IS_ENGINE_READY with 0th engineKey

* fixme to todo

* fix comment

* assert engineKey, engineId, styleId is not undefined

* fix comment

* remove unused import

* fix CharacterOrderDialog

* CharacterOrderDialog: change icon based on selected style

* remove .value in template

* DefaultStyleSelectDialog: multiple engine (allow duplicated styleId)

* LibraryPolicy: multiple engine

* fix log

* CharacterOrderDialog: allow duplication of styleId when speakerUuid is different

* fix comment

* split icon change pr

* fix comment

* state.engineInfos to Record<string, EngineInfo> from EngineInfo[]

* fix dictionary actions to use state.engineKeys instead of engineInfos

* npm run fmt

* initialize engineStates with STARTING

* npm run fmt

* GENERATE_AUDIO_ITEM: add engineKey undefined check

* use strict equality for engineKey undefined check

* add comment to describe loop behavior

* add STARTING assert test

* remove unneeded undefined check

* DictionaryManageDialog: temporarily use first engineKey

* flatMap engineInfos -> engineKeys

* loop engineInfos -> engineKeys

* add characterInfos assert.isObject

* fix DictionaryManageDialog working for first engine

* SETUP_ENGINE_SPEAKER: add engineKey (fix character init)

* fix assertion compare

* fix assert message

* project migration version 0.12 -> 0.13

* add comment

* LOAD_USER_DICT: add engineKey

* getFlattenCharacterInfos func

* GENERATE_AUDIO_ITEM: (engineId, styleId) assertion

* fix message

* add comment

* 動くように

* engineKey -> engineId

* engineKey to engineId

* key -> id

* id -> uuid

* default engine id

* Update src/store/audio.ts

* getFlattenCharacterInfos関数をGETTERSに

* engineidなどの設計方針を記載

* USER_ORDERED_CHARACTER_INFOSのとこ修正

* if ((payload.engineId === undefined) != (payload.styleId === undefined))

* home.vueのとこをflattenCharacterInfosに

* style.styleName || "ノーマル"

* speakerUuid・styleIdが一致するCharacterInfoを持つエンジンを探す

* ID->Id

* 追加修正

* 下のengineIdで上書き定義しないように

* FIXMEコメント追加、0.14に変更

* AudioItemにSpeakerIdを追加したい旨を追記

* ですます調に

* StyleId は現在整数値型になっていますが、将来的にはUuidにしたいと考えています。

* .

Co-authored-by: aoirint <[email protected]>
  • Loading branch information
Hiroshiba and aoirint authored Aug 23, 2022
1 parent da5201e commit 5af6ed3
Show file tree
Hide file tree
Showing 13 changed files with 429 additions and 188 deletions.
15 changes: 15 additions & 0 deletions docs/設計方針.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,18 @@
ダイアログは外部 API とみなし、Vuex の Store の Action 内から呼ぶことを許容しています。
これは、ダイアログの結果によってダイアログ前後の挙動を 1 つの Action に書けるという利便性を取ったためです。
ダイアログの種類によっては View に直接作用してダイアログ UI を表示するものもありますが、これも許容することにしています。

## EngineId、SpeakerId、StyleId

EngineId はエンジンが持つ ID で、世界で唯一かつ不変です。
SpeakerId は話者が持つ ID で、世界で唯一かつ不変。エンジン間でも同じ ID を持ちます。
StyleId はスタイルごとの ID で、話者ごとに唯一であれば良いです。

声を一意に決めるには、(EngineId, SpeakerId, StyleId)の3組が揃っている必要がある、という仕様を目指しています。
現状は、音声合成APIに SpeakerId 引数が無いため、(EngineId, StyleId)の2組で一意に声が決まっています。

VOICEVOX は歴史的経緯により、 SpeakerId と StyleId が混同していることがあります。
特に型が整数値になっている SpeakerId は StyleId と混同している場合があります。
(エンジン API の SpeakerId 引数に StyleId を渡したりなど。)

StyleId は現在整数値型になっていますが、将来的にはUuidにしたいと考えています。
40 changes: 33 additions & 7 deletions src/components/AudioCell.vue
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"
@click="
changeStyleId(
characterInfo.metas.speakerUuid,
getDefaultStyle(characterInfo.metas.speakerUuid).styleId
)
"
Expand Down Expand Up @@ -97,7 +98,12 @@
v-close-popup
active-class="selected-character-item"
:active="style.styleId === selectedStyle.styleId"
@click="changeStyleId(style.styleId)"
@click="
changeStyleId(
characterInfo.metas.speakerUuid,
style.styleId
)
"
>
<q-avatar rounded size="2rem" class="q-mr-md">
<q-img
Expand Down Expand Up @@ -198,11 +204,11 @@ export default defineComponent({
const selectedCharacterInfo = computed(() =>
userOrderedCharacterInfos.value !== undefined &&
audioItem.value.engineId !== undefined &&
audioItem.value.styleId !== undefined
? userOrderedCharacterInfos.value.find((info) =>
info.metas.styles.find(
(style) => style.styleId === audioItem.value.styleId
)
? store.getters.CHARACTER_INFO(
audioItem.value.engineId,
audioItem.value.styleId
)
: undefined
);
Expand Down Expand Up @@ -256,13 +262,26 @@ export default defineComponent({
}
};
const changeStyleId = (styleId: number) => {
const changeStyleId = (speakerUuid: string, styleId: number) => {
// FIXME: 同一キャラが複数エンジンにまたがっているとき、順番が先のエンジンが必ず選択される
const engineId = store.state.engineIds.find((_engineId) =>
(store.state.characterInfos[_engineId] ?? []).some(
(characterInfo) => characterInfo.metas.speakerUuid === speakerUuid
)
);
if (engineId === undefined)
throw new Error(
`No engineId for target character style (speakerUuid == ${speakerUuid}, styleId == ${styleId})`
);
store.dispatch("COMMAND_CHANGE_STYLE_ID", {
audioKey: props.audioKey,
engineId,
styleId,
});
};
const getDefaultStyle = (speakerUuid: string) => {
// FIXME: 同一キャラが複数エンジンにまたがっているとき、順番が先のエンジンが必ず選択される
const characterInfo = userOrderedCharacterInfos.value?.find(
(info) => info.metas.speakerUuid === speakerUuid
);
Expand Down Expand Up @@ -316,10 +335,17 @@ export default defineComponent({
await pushAudioText();
}
const engineId = audioItem.value.engineId;
if (engineId === undefined)
throw new Error("assert engineId !== undefined");
const styleId = audioItem.value.styleId;
if (styleId == undefined) throw new Error("styleId == undefined");
if (styleId === undefined)
throw new Error("assert styleId !== undefined");
const audioKeys = await store.dispatch("COMMAND_PUT_TEXTS", {
texts,
engineId,
styleId,
prevAudioKey,
});
Expand Down
11 changes: 5 additions & 6 deletions src/components/CharacterPortrait.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,17 @@ export default defineComponent({
const store = useStore();
const characterInfo = computed(() => {
const characterInfos = store.state.characterInfos || [];
const activeAudioKey: string | undefined = store.getters.ACTIVE_AUDIO_KEY;
const audioItem = activeAudioKey
? store.state.audioItems[activeAudioKey]
: undefined;
const engineId = audioItem?.engineId;
const styleId = audioItem?.styleId;
return styleId !== undefined
? characterInfos.find((info) =>
info.metas.styles.find((style) => style.styleId === styleId)
)
: undefined;
if (engineId === undefined || styleId === undefined) return undefined;
return store.getters.CHARACTER_INFO(engineId, styleId);
});
const characterName = computed(() => {
Expand Down
25 changes: 24 additions & 1 deletion src/components/DictionaryManageDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,8 @@ export default defineComponent({
const store = useStore();
const $q = useQuasar();
const engineIdComputed = computed(() => store.state.engineIds[0]); // TODO: 複数エンジン対応
const dictionaryManageDialogOpenedComputed = computed({
get: () => props.modelValue,
set: (val) => emit("update:modelValue", val),
Expand All @@ -282,10 +284,16 @@ export default defineComponent({
};
const loadingDictProcess = async () => {
const engineId = engineIdComputed.value;
if (engineId === undefined)
throw new Error(`assert engineId !== undefined`);
loadingDict.value = true;
try {
userDict.value = await createUILockAction(
store.dispatch("LOAD_USER_DICT")
store.dispatch("LOAD_USER_DICT", {
engineId,
})
);
} catch {
$q.dialog({
Expand Down Expand Up @@ -356,6 +364,10 @@ export default defineComponent({
surface.value = convertHankakuToZenkaku(text);
};
const setYomi = async (text: string, changeWord?: boolean) => {
const engineId = engineIdComputed.value;
if (engineId === undefined)
throw new Error(`assert engineId !== undefined`);
// テキスト長が0の時にエラー表示にならないように、テキスト長を考慮する
isOnlyHiraOrKana.value = !text.length || kanaRegex.test(text);
// 読みが変更されていない場合は、アクセントフレーズに変更を加えない
Expand All @@ -378,6 +390,7 @@ export default defineComponent({
await createUILockAction(
store.dispatch("FETCH_ACCENT_PHRASES", {
text: text + "ガ'",
engineId,
styleId: styleId.value,
isKana: true,
})
Expand All @@ -396,12 +409,17 @@ export default defineComponent({
};
const changeAccent = async (_: number, accent: number) => {
const engineId = engineIdComputed.value;
if (engineId === undefined)
throw new Error(`assert engineId !== undefined`);
if (accentPhrase.value) {
accentPhrase.value.accent = accent;
accentPhrase.value = (
await createUILockAction(
store.dispatch("FETCH_MORA_DATA", {
accentPhrases: [accentPhrase.value],
engineId,
styleId: styleId.value,
})
)
Expand All @@ -413,6 +431,10 @@ export default defineComponent({
audioElem.pause();
const play = async () => {
const engineId = engineIdComputed.value;
if (engineId === undefined)
throw new Error(`assert engineId !== undefined`);
if (!accentPhrase.value) return;
nowGenerating.value = true;
const query: AudioQuery = {
Expand All @@ -429,6 +451,7 @@ export default defineComponent({
const audioItem: AudioItem = {
text: yomi.value,
engineId,
styleId: styleId.value,
query,
};
Expand Down
6 changes: 4 additions & 2 deletions src/components/LibraryPolicy.vue
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ export default defineComponent({
const store = useStore();
const md = useMarkdownIt();
const characterInfos = computed(() => store.state.characterInfos);
const flattenCharacterInfos = computed(
() => store.getters.GET_FLATTEN_CHARACTER_INFOS
);
const convertMarkdown = (text: string) => {
return md.render(text);
Expand All @@ -62,7 +64,7 @@ export default defineComponent({
};
return {
characterInfos,
characterInfos: flattenCharacterInfos,
convertMarkdown,
selectCharacterInfIndex,
detailIndex,
Expand Down
Loading

0 comments on commit 5af6ed3

Please sign in to comment.