From a22046c731dbd88794a0e2e0d0a632b977bf5e29 Mon Sep 17 00:00:00 2001 From: sabonerune <102559104+sabonerune@users.noreply.github.com> Date: Mon, 6 Feb 2023 21:58:41 +0900 Subject: [PATCH] =?UTF-8?q?ADD:=20AudioItem=E3=81=ABSpeakerId=E3=82=92?= =?UTF-8?q?=E8=BF=BD=E5=8A=A0=20(#1092)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Hiroshiba --- src/components/AudioCell.vue | 21 +- src/components/AudioDetail.vue | 8 +- src/components/AudioInfo.vue | 24 +-- src/components/CharacterPortrait.vue | 8 +- src/components/DictionaryManageDialog.vue | 31 ++- src/store/audio.ts | 233 +++++++--------------- src/store/index.ts | 20 +- src/store/project.ts | 73 ++++++- src/store/type.ts | 25 +-- src/views/EditorHome.vue | 22 +- 10 files changed, 202 insertions(+), 263 deletions(-) diff --git a/src/components/AudioCell.vue b/src/components/AudioCell.vue index cbe3ab7f47..7450ef6a27 100644 --- a/src/components/AudioCell.vue +++ b/src/components/AudioCell.vue @@ -90,12 +90,9 @@ const uiLocked = computed(() => store.getters.UI_LOCKED); const selectedVoice = computed({ get() { - const engineId = audioItem.value.engineId; - const styleId = audioItem.value.styleId; + const { engineId, styleId } = audioItem.value.voice; if ( - engineId == undefined || - styleId == undefined || !store.state.engineIds.some((storeEngineId) => storeEngineId === engineId) ) return undefined; @@ -110,10 +107,9 @@ const selectedVoice = computed({ }, set(voice: Voice | undefined) { if (voice == undefined) return; - store.dispatch("COMMAND_CHANGE_STYLE_ID", { + store.dispatch("COMMAND_CHANGE_VOICE", { audioKey: props.audioKey, - engineId: voice.engineId, - styleId: voice.styleId, + voice, }); }, }); @@ -179,18 +175,9 @@ const pasteOnAudioCell = async (event: ClipboardEvent) => { 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("assert styleId !== undefined"); - const audioKeys = await store.dispatch("COMMAND_PUT_TEXTS", { texts, - engineId, - styleId, + voice: audioItem.value.voice, prevAudioKey, }); if (audioKeys) diff --git a/src/components/AudioDetail.vue b/src/components/AudioDetail.vue index adebc5713e..04604c99ae 100644 --- a/src/components/AudioDetail.vue +++ b/src/components/AudioDetail.vue @@ -285,9 +285,11 @@ const $q = useQuasar(); const supportedFeatures = computed( () => - (audioItem.value?.engineId && - store.state.engineIds.some((id) => id === audioItem.value.engineId) && - store.state.engineManifests[audioItem.value?.engineId] + (audioItem.value?.voice.engineId && + store.state.engineIds.some( + (id) => id === audioItem.value.voice.engineId + ) && + store.state.engineManifests[audioItem.value.voice.engineId] .supportedFeatures) as EngineManifest["supportedFeatures"] | undefined ); diff --git a/src/components/AudioInfo.vue b/src/components/AudioInfo.vue index 0978ced4df..5c96add258 100644 --- a/src/components/AudioInfo.vue +++ b/src/components/AudioInfo.vue @@ -507,9 +507,10 @@ const query = computed(() => audioItem.value?.query); const supportedFeatures = computed( () => - (audioItem.value?.engineId && - store.state.engineIds.some((id) => id === audioItem.value.engineId) && - store.state.engineManifests[audioItem.value?.engineId] + (store.state.engineIds.some( + (id) => id === audioItem.value.voice.engineId + ) && + store.state.engineManifests[audioItem.value.voice.engineId] .supportedFeatures) as EngineManifest["supportedFeatures"] | undefined ); @@ -652,14 +653,10 @@ const morphingTargetEngines = store.getters.MORPHING_SUPPORTED_ENGINES; // モーフィング可能なターゲット一覧を取得 watchEffect(() => { - if ( - audioItem.value != undefined && - audioItem.value.engineId != undefined && - audioItem.value.styleId != undefined - ) { + if (audioItem.value != undefined) { store.dispatch("LOAD_MORPHABLE_TARGETS", { - engineId: audioItem.value.engineId, - baseStyleId: audioItem.value.styleId, + engineId: audioItem.value.voice.engineId, + baseStyleId: audioItem.value.voice.styleId, }); } }); @@ -669,11 +666,8 @@ const morphingTargetCharacters = computed(() => { if (allCharacterInfos == undefined) throw new Error("USER_ORDERED_CHARACTER_INFOS == undefined"); - const baseEngineId = audioItem.value.engineId; - const baseStyleId = audioItem.value.styleId; - if (baseEngineId === undefined || baseStyleId == undefined) { - throw new Error("baseEngineId == undefined || baseStyleId == undefined"); - } + const baseEngineId = audioItem.value.voice.engineId; + const baseStyleId = audioItem.value.voice.styleId; // モーフィング対象リストを問い合わせていないときはとりあえず空欄を表示 // FIXME: そもそもモーフィングUIを表示しないようにする diff --git a/src/components/CharacterPortrait.vue b/src/components/CharacterPortrait.vue index eaa57159d5..1bdfa18f64 100644 --- a/src/components/CharacterPortrait.vue +++ b/src/components/CharacterPortrait.vue @@ -27,8 +27,8 @@ export default defineComponent({ ? store.state.audioItems[activeAudioKey] : undefined; - const engineId = audioItem?.engineId; - const styleId = audioItem?.styleId; + const engineId = audioItem?.voice.engineId; + const styleId = audioItem?.voice.styleId; if ( engineId === undefined || @@ -47,7 +47,7 @@ export default defineComponent({ ? store.state.audioItems[activeAudioKey] : undefined; - const styleId = audioItem?.styleId; + const styleId = audioItem?.voice.styleId; const style = characterInfo.value?.metas.styles.find( (style) => style.styleId === styleId ); @@ -65,7 +65,7 @@ export default defineComponent({ const audioItem = activeAudioKey ? store.state.audioItems[activeAudioKey] : undefined; - const engineId = audioItem?.engineId ?? store.state.engineIds[0]; + const engineId = audioItem?.voice.engineId ?? store.state.engineIds[0]; const engineManifest = store.state.engineManifests[engineId]; const engineInfo = store.state.engineInfos[engineId]; return engineManifest ? engineManifest.brandName : engineInfo.name; diff --git a/src/components/DictionaryManageDialog.vue b/src/components/DictionaryManageDialog.vue index 2726c23d16..bb8ee68722 100644 --- a/src/components/DictionaryManageDialog.vue +++ b/src/components/DictionaryManageDialog.vue @@ -360,16 +360,15 @@ const selectedId = ref(""); const surface = ref(""); const yomi = ref(""); -const styleId = computed(() => { - if (!store.getters.USER_ORDERED_CHARACTER_INFOS) return 0; - return store.getters.USER_ORDERED_CHARACTER_INFOS[0].metas.styles[0].styleId; -}); -const engineIdComputed = computed(() => { +const voiceComputed = computed(() => { + if (store.getters.USER_ORDERED_CHARACTER_INFOS == undefined) + throw new Error("assert USER_ORDERED_CHARACTER_INFOS"); if (store.state.engineIds.length === 0) throw new Error("assert engineId.length > 0"); - if (!store.getters.USER_ORDERED_CHARACTER_INFOS) - throw new Error("assert USER_ORDERED_CHARACTER_INFOS"); - return store.getters.USER_ORDERED_CHARACTER_INFOS[0].metas.styles[0].engineId; + const characterInfo = store.getters.USER_ORDERED_CHARACTER_INFOS[0].metas; + const speakerId = characterInfo.speakerUuid; + const { engineId, styleId } = characterInfo.styles[0]; + return { engineId, speakerId, styleId }; }); const kanaRegex = createKanaRegex(); @@ -393,8 +392,7 @@ const setSurface = (text: string) => { surface.value = convertHankakuToZenkaku(text); }; const setYomi = async (text: string, changeWord?: boolean) => { - const engineId = engineIdComputed.value; - if (engineId === undefined) throw new Error(`assert engineId !== undefined`); + const { engineId, styleId } = voiceComputed.value; // テキスト長が0の時にエラー表示にならないように、テキスト長を考慮する isOnlyHiraOrKana.value = !text.length || kanaRegex.test(text); @@ -419,7 +417,7 @@ const setYomi = async (text: string, changeWord?: boolean) => { store.dispatch("FETCH_ACCENT_PHRASES", { text: text + "ガ'", engineId, - styleId: styleId.value, + styleId, isKana: true, }) ) @@ -434,8 +432,7 @@ const setYomi = async (text: string, changeWord?: boolean) => { }; const changeAccent = async (_: number, accent: number) => { - const engineId = engineIdComputed.value; - if (engineId === undefined) throw new Error(`assert engineId !== undefined`); + const { engineId, styleId } = voiceComputed.value; if (accentPhrase.value) { accentPhrase.value.accent = accent; @@ -444,7 +441,7 @@ const changeAccent = async (_: number, accent: number) => { store.dispatch("FETCH_MORA_DATA", { accentPhrases: [accentPhrase.value], engineId, - styleId: styleId.value, + styleId, }) ) )[0]; @@ -455,16 +452,12 @@ const audioElem = new Audio(); 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 audioItem = await store.dispatch("GENERATE_AUDIO_ITEM", { text: yomi.value, - engineId, - styleId: styleId.value, + voice: voiceComputed.value, }); if (audioItem.query == undefined) diff --git a/src/store/audio.ts b/src/store/audio.ts index b52130d3df..ef37160987 100644 --- a/src/store/audio.ts +++ b/src/store/audio.ts @@ -20,6 +20,7 @@ import { MoraDataType, MorphingInfo, StyleInfo, + Voice, WriteFileErrorResult, } from "@/type/preload"; import Encoding from "encoding-japanese"; @@ -42,10 +43,8 @@ async function generateUniqueIdAndQuery( audioItem = JSON.parse(JSON.stringify(audioItem)) as AudioItem; const audioQuery = audioItem.query; if (audioQuery != undefined) { - if (audioItem.engineId == undefined) - throw new Error(`audioItem.engineId is undefined`); audioQuery.outputSamplingRate = - state.engineSettings[audioItem.engineId].outputSamplingRate; + state.engineSettings[audioItem.voice.engineId].outputSamplingRate; audioQuery.outputStereo = state.savingSetting.outputStereo; } @@ -53,8 +52,7 @@ async function generateUniqueIdAndQuery( JSON.stringify([ audioItem.text, audioQuery, - audioItem.engineId, - audioItem.styleId, + audioItem.voice, audioItem.morphingInfo, state.experimentalSetting.enableInterrogativeUpspeak, // このフラグが違うと、同じAudioQueryで違う音声が生成されるので追加 ]) @@ -71,58 +69,52 @@ function parseTextFile( defaultStyleIds: DefaultStyleId[], userOrderedCharacterInfos: CharacterInfo[] ): AudioItem[] { - const characters = new Map(); - const uuid2StyleIds = new Map< - string, - { engineId: string; styleId: number } - >(); + const name2Voice = new Map(); + const uuid2Voice = new Map(); for (const defaultStyleId of defaultStyleIds) { - const speakerUuid = defaultStyleId.speakerUuid; + const speakerId = defaultStyleId.speakerUuid; const engineId = defaultStyleId.engineId; const styleId = defaultStyleId.defaultStyleId; - uuid2StyleIds.set(speakerUuid, { engineId, styleId }); + uuid2Voice.set(speakerId, { engineId, speakerId, styleId }); } // setup default characters for (const characterInfo of userOrderedCharacterInfos) { const uuid = characterInfo.metas.speakerUuid; - const style = uuid2StyleIds.get(uuid); + const voice = uuid2Voice.get(uuid); const speakerName = characterInfo.metas.speakerName; - if (style == undefined) + if (voice == undefined) throw new Error(`style is undefined. speakerUuid: ${uuid}`); - characters.set(speakerName, { - engineId: style.engineId, - styleId: style.styleId, - }); + name2Voice.set(speakerName, voice); } // setup characters with style name for (const characterInfo of userOrderedCharacterInfos) { for (const style of characterInfo.metas.styles) { - characters.set( + name2Voice.set( `${characterInfo.metas.speakerName}(${style.styleName || "ノーマル"})`, - { engineId: style.engineId, styleId: style.styleId } + { + engineId: style.engineId, + speakerId: characterInfo.metas.speakerUuid, + styleId: style.styleId, + } ); } } - if (!characters.size) return []; + if (!name2Voice.size) return []; const audioItems: AudioItem[] = []; const seps = [",", "\r\n", "\n"]; - let lastStyle = uuid2StyleIds.get( + let lastVoice = uuid2Voice.get( userOrderedCharacterInfos[0].metas.speakerUuid ); - if (lastStyle == undefined) throw new Error(`lastStyle is undefined.`); + if (lastVoice == undefined) throw new Error(`lastStyle is undefined.`); for (const splitText of body.split(new RegExp(`${seps.join("|")}`, "g"))) { - const styleId = characters.get(splitText); - if (styleId !== undefined) { - lastStyle = styleId; + const voice = name2Voice.get(splitText); + if (voice !== undefined) { + lastVoice = voice; continue; } - audioItems.push({ - text: splitText, - engineId: lastStyle.engineId, - styleId: lastStyle.styleId, - }); + audioItems.push({ text: splitText, voice: lastVoice }); } return audioItems; } @@ -134,21 +126,16 @@ function buildFileName(state: State, audioKey: string) { const index = state.audioKeys.indexOf(audioKey); const audioItem = state.audioItems[audioKey]; - if (audioItem.engineId === undefined) - throw new Error("asssrt audioItem.engineId !== undefined"); - if (audioItem.styleId === undefined) - throw new Error("assert audioItem.styleId !== undefined"); - const character = getCharacterInfo( state, - audioItem.engineId, - audioItem.styleId + audioItem.voice.engineId, + audioItem.voice.styleId ); if (character === undefined) throw new Error("assert character !== undefined"); const style = character.metas.styles.find( - (style) => style.styleId === audioItem.styleId + (style) => style.styleId === audioItem.voice.styleId ); if (style === undefined) throw new Error("assert style !== undefined"); @@ -484,8 +471,7 @@ export const audioStore = createPartialStore({ { state, getters, dispatch }, payload: { text?: string; - engineId?: string; - styleId?: number; + voice?: Voice; presetKey?: string; baseAudioItem?: AudioItem; } @@ -497,36 +483,26 @@ export const audioStore = createPartialStore({ throw new Error("state.defaultStyleIds == undefined"); if (getters.USER_ORDERED_CHARACTER_INFOS == undefined) throw new Error("state.characterInfos == undefined"); - const userOrderedCharacterInfos = getters.USER_ORDERED_CHARACTER_INFOS; const text = payload.text ?? ""; - const engineId = payload.engineId ?? state.engineIds[0]; - - // FIXME: engineIdも含めて探査する - const styleId = - payload.styleId ?? - state.defaultStyleIds[ - state.defaultStyleIds.findIndex( - (x) => - x.speakerUuid === userOrderedCharacterInfos[0].metas.speakerUuid // FIXME: defaultStyleIds内にspeakerUuidがない場合がある - ) - ].defaultStyleId; + const defaultStyleId = state.defaultStyleIds[0]; + const voice = payload.voice ?? { + engineId: defaultStyleId.engineId, + speakerId: defaultStyleId.speakerUuid, + styleId: defaultStyleId.defaultStyleId, + }; const baseAudioItem = payload.baseAudioItem; - const query = getters.IS_ENGINE_READY(engineId) + const query = getters.IS_ENGINE_READY(voice.engineId) ? await dispatch("FETCH_AUDIO_QUERY", { text, - engineId, - styleId, + engineId: voice.engineId, + styleId: voice.styleId, }).catch(() => undefined) : undefined; - const audioItem: AudioItem = { - text, - engineId, - styleId, - }; + const audioItem: AudioItem = { text, voice }; if (query != undefined) { audioItem.query = query; } @@ -764,14 +740,10 @@ export const audioStore = createPartialStore({ VALID_MORPHING_INFO: { getter: (state) => (audioItem: AudioItem) => { - if ( - audioItem.engineId == undefined || - audioItem.styleId == undefined || - audioItem.morphingInfo?.targetStyleId == undefined - ) - return false; + if (audioItem.morphingInfo?.targetStyleId == undefined) return false; + const { engineId, styleId } = audioItem.voice; const info = - state.morphableTargetsInfo[audioItem.engineId]?.[audioItem.styleId]?.[ + state.morphableTargetsInfo[engineId]?.[styleId]?.[ audioItem.morphingInfo.targetStyleId ]; if (info == undefined) return false; @@ -819,17 +791,9 @@ export const audioStore = createPartialStore({ }, }, - SET_AUDIO_STYLE_ID: { - mutation( - state, - { - audioKey, - engineId, - styleId, - }: { audioKey: string; engineId: string; styleId: number } - ) { - state.audioItems[audioKey].engineId = engineId; - state.audioItems[audioKey].styleId = styleId; + SET_AUDIO_VOICE: { + mutation(state, { audioKey, voice }: { audioKey: string; voice: Voice }) { + state.audioItems[audioKey].voice = voice; }, }, @@ -1150,9 +1114,7 @@ export const audioStore = createPartialStore({ { dispatch, getters, state }, { audioItem }: { audioItem: AudioItem } ) => { - const engineId = audioItem.engineId; - if (engineId === undefined) - throw new Error("engineId is not defined for audioItem"); + const engineId = audioItem.voice.engineId; const [id, audioQuery] = await generateUniqueIdAndQuery( state, @@ -1161,9 +1123,7 @@ export const audioStore = createPartialStore({ if (audioQuery == undefined) throw new Error("audioQuery is not defined for audioItem"); - const speaker = audioItem.styleId; - if (speaker == undefined) - throw new Error("speaker is not defined for audioItem"); + const speaker = audioItem.voice.styleId; const engineAudioQuery = convertAudioQueryFromEditorToEngine( audioQuery, @@ -1641,8 +1601,8 @@ export const audioStore = createPartialStore({ const texts: string[] = []; for (const audioKey of state.audioKeys) { - const styleId = state.audioItems[audioKey].styleId; - const engineId = state.audioItems[audioKey].engineId; + const styleId = state.audioItems[audioKey].voice.styleId; + const engineId = state.audioItems[audioKey].voice.engineId; if (!engineId) { throw new Error("engineId is undefined"); } @@ -1964,14 +1924,8 @@ export const audioCommandStore = transformCommandStore( { state, commit, dispatch }, { audioKey, text }: { audioKey: string; text: string } ) { - const engineId = state.audioItems[audioKey].engineId; - if (engineId === undefined) - throw new Error("assert engineId !== undefined"); - - const styleId = state.audioItems[audioKey].styleId; - if (styleId === undefined) - throw new Error("assert styleId !== undefined"); - + const engineId = state.audioItems[audioKey].voice.engineId; + const styleId = state.audioItems[audioKey].voice.styleId; const query = state.audioItems[audioKey].query; try { if (query !== undefined) { @@ -2013,19 +1967,18 @@ export const audioCommandStore = transformCommandStore( }, }, - COMMAND_CHANGE_STYLE_ID: { + COMMAND_CHANGE_VOICE: { mutation( draft, - payload: { engineId: string; styleId: number; audioKey: string } & ( + payload: { audioKey: string; voice: Voice } & ( | { update: "StyleId" } | { update: "AccentPhrases"; accentPhrases: AccentPhrase[] } | { update: "AudioQuery"; query: AudioQuery } ) ) { - audioStore.mutations.SET_AUDIO_STYLE_ID(draft, { + audioStore.mutations.SET_AUDIO_VOICE(draft, { audioKey: payload.audioKey, - engineId: payload.engineId, - styleId: payload.styleId, + voice: payload.voice, }); if (payload.update == "AccentPhrases") { audioStore.mutations.SET_ACCENT_PHRASES(draft, { @@ -2044,13 +1997,11 @@ export const audioCommandStore = transformCommandStore( }, async action( { state, dispatch, commit }, - { - audioKey, - engineId, - styleId, - }: { audioKey: string; engineId: string; styleId: number } + { audioKey, voice }: { audioKey: string; voice: Voice } ) { const query = state.audioItems[audioKey].query; + const engineId = voice.engineId; + const styleId = voice.styleId; try { await dispatch("SETUP_SPEAKER", { audioKey, engineId, styleId }); @@ -2064,10 +2015,9 @@ export const audioCommandStore = transformCommandStore( styleId, } ); - commit("COMMAND_CHANGE_STYLE_ID", { - engineId, - styleId, + commit("COMMAND_CHANGE_VOICE", { audioKey, + voice, update: "AccentPhrases", accentPhrases: newAccentPhrases, }); @@ -2078,19 +2028,17 @@ export const audioCommandStore = transformCommandStore( engineId, styleId, }); - commit("COMMAND_CHANGE_STYLE_ID", { - engineId, - styleId, + commit("COMMAND_CHANGE_VOICE", { audioKey, + voice, update: "AudioQuery", query, }); } } catch (error) { - commit("COMMAND_CHANGE_STYLE_ID", { - engineId, - styleId, + commit("COMMAND_CHANGE_VOICE", { audioKey, + voice, update: "StyleId", }); throw error; @@ -2127,13 +2075,8 @@ export const audioCommandStore = transformCommandStore( newAccentPhrases[accentPhraseIndex].accent = accent; try { - const engineId = state.audioItems[audioKey].engineId; - if (engineId === undefined) - throw new Error("assert engineId !== undefined"); - - const styleId = state.audioItems[audioKey].styleId; - if (styleId === undefined) - throw new Error("assert styleId !== undefined"); + const engineId = state.audioItems[audioKey].voice.engineId; + const styleId = state.audioItems[audioKey].voice.styleId; const resultAccentPhrases: AccentPhrase[] = await dispatch( "FETCH_AND_COPY_MORA_DATA", @@ -2180,13 +2123,8 @@ export const audioCommandStore = transformCommandStore( const { audioKey, accentPhraseIndex } = payload; const query = state.audioItems[audioKey].query; - const engineId = state.audioItems[audioKey].engineId; - if (engineId === undefined) - throw new Error("assert engineId !== undefined"); - - const styleId = state.audioItems[audioKey].styleId; - if (styleId === undefined) - throw new Error("assert styleId !== undefined"); + const engineId = state.audioItems[audioKey].voice.engineId; + const styleId = state.audioItems[audioKey].voice.styleId; if (query === undefined) { throw Error( @@ -2311,13 +2249,8 @@ export const audioCommandStore = transformCommandStore( popUntilPause: boolean; } ) { - const engineId = state.audioItems[audioKey].engineId; - if (engineId === undefined) - throw new Error("assert engineId !== undefined"); - - const styleId = state.audioItems[audioKey].styleId; - if (styleId === undefined) - throw new Error("assert styleId !== undefined"); + const engineId = state.audioItems[audioKey].voice.engineId; + const styleId = state.audioItems[audioKey].voice.styleId; let newAccentPhrasesSegment: AccentPhrase[] | undefined = undefined; @@ -2411,13 +2344,8 @@ export const audioCommandStore = transformCommandStore( COMMAND_RESET_MORA_PITCH_AND_LENGTH: { async action({ state, dispatch, commit }, { audioKey }) { - const engineId = state.audioItems[audioKey].engineId; - if (engineId === undefined) - throw new Error("assert engineId !== undefined"); - - const styleId = state.audioItems[audioKey].styleId; - if (styleId === undefined) - throw new Error("assert styleId !== undefined"); + const engineId = state.audioItems[audioKey].voice.engineId; + const styleId = state.audioItems[audioKey].voice.styleId; const query = state.audioItems[audioKey].query; if (query === undefined) throw new Error("assert query !== undefined"); @@ -2440,11 +2368,8 @@ export const audioCommandStore = transformCommandStore( { state, dispatch, commit }, { audioKey, accentPhraseIndex } ) { - const engineId = state.audioItems[audioKey].engineId; - if (engineId == undefined) throw new Error("engineId == undefined"); - - const styleId = state.audioItems[audioKey].styleId; - if (styleId == undefined) throw new Error("styleId == undefined"); + const engineId = state.audioItems[audioKey].voice.engineId; + const styleId = state.audioItems[audioKey].voice.styleId; const query = state.audioItems[audioKey].query; if (query == undefined) throw new Error("query == undefined"); @@ -2763,7 +2688,7 @@ export const audioCommandStore = transformCommandStore( if (!getters.USER_ORDERED_CHARACTER_INFOS) throw new Error("USER_ORDERED_CHARACTER_INFOS == undefined"); - for (const { text, engineId, styleId } of parseTextFile( + for (const { text, voice } of parseTextFile( body, state.defaultStyleIds, getters.USER_ORDERED_CHARACTER_INFOS @@ -2773,8 +2698,7 @@ export const audioCommandStore = transformCommandStore( audioItems.push( await dispatch("GENERATE_AUDIO_ITEM", { text, - engineId, - styleId, + voice, baseAudioItem, }) ); @@ -2816,13 +2740,11 @@ export const audioCommandStore = transformCommandStore( { prevAudioKey, texts, - engineId, - styleId, + voice, }: { prevAudioKey: string; texts: string[]; - engineId: string; - styleId: number; + voice: Voice; } ) => { const audioKeyItemPairs: { @@ -2841,8 +2763,7 @@ export const audioCommandStore = transformCommandStore( //パラメータ引き継ぎがOFFの場合、baseAudioItemがundefinedになっているのでパラメータ引き継ぎは行われない const audioItem = await dispatch("GENERATE_AUDIO_ITEM", { text, - engineId, - styleId, + voice, baseAudioItem, presetKey: basePresetKey, }); diff --git a/src/store/index.ts b/src/store/index.ts index 5cc923c66b..556392c8d7 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -214,16 +214,10 @@ export const indexStore = createPartialStore({ if (state.audioKeys.length === 1) { const audioItem = state.audioItems[state.audioKeys[0]]; if (audioItem.text === "") { - if (audioItem.engineId === undefined) - throw new Error("assert audioItem.engineId !== undefined"); - - if (audioItem.styleId === undefined) - throw new Error("assert audioItem.styleId !== undefined"); - const characterInfo = getCharacterInfo( state, - audioItem.engineId, - audioItem.styleId + audioItem.voice.engineId, + audioItem.voice.styleId ); if (characterInfo === undefined) @@ -232,9 +226,15 @@ export const indexStore = createPartialStore({ const speakerUuid = characterInfo.metas.speakerUuid; const defaultStyleId = defaultStyleIds.find( (styleId) => speakerUuid == styleId.speakerUuid - )?.defaultStyleId; + ); + if (defaultStyleId == undefined) + throw new Error("defaultStyleId == undefined"); - audioItem.styleId = defaultStyleId; + audioItem.voice = { + engineId: defaultStyleId.engineId, + speakerId: defaultStyleId.speakerUuid, + styleId: defaultStyleId.defaultStyleId, + }; } } }, diff --git a/src/store/project.ts b/src/store/project.ts index f0cb795b1a..e0a6c577b5 100755 --- a/src/store/project.ts +++ b/src/store/project.ts @@ -233,6 +233,41 @@ export const projectStore = createPartialStore({ } } + if ( + semver.satisfies(projectAppVersion, "<0.15", semverSatisfiesOptions) + ) { + const characterInfos = context.getters.USER_ORDERED_CHARACTER_INFOS; + if (characterInfos == undefined) + throw new Error("USER_ORDERED_CHARACTER_INFOS == undefined"); + for (const audioItemsKey in projectData.audioItems) { + const audioItem = projectData.audioItems[audioItemsKey]; + if (audioItem.voice == undefined) { + const oldEngineId = audioItem.engineId; + const oldStyleId = audioItem.styleId; + const chracterinfo = characterInfos.find((characterInfo) => + characterInfo.metas.styles.some( + (styeleinfo) => + styeleinfo.engineId === audioItem.engineId && + styeleinfo.styleId === audioItem.styleId + ) + ); + if (chracterinfo == undefined) + throw new Error( + `chracterinfo == undefined: ${oldEngineId}, ${oldStyleId}` + ); + const speakerId = chracterinfo.metas.speakerUuid; + audioItem.voice = { + engineId: oldEngineId, + speakerId, + styleId: oldStyleId, + }; + + delete audioItem.engineId; + delete audioItem.styleId; + } + } + } + // Validation check const parsedProjectData = projectSchema.parse(projectData); if ( @@ -248,23 +283,38 @@ export const projectStore = createPartialStore({ if ( !parsedProjectData.audioKeys.every( (audioKey) => - parsedProjectData.audioItems[audioKey].engineId != undefined + parsedProjectData.audioItems[audioKey].voice != undefined ) ) { - throw new Error( - 'Every audioItem should have a "engineId" attribute.' - ); + throw new Error('Every audioItem should have a "voice" attribute.'); + } + if ( + !parsedProjectData.audioKeys.every( + (audioKey) => + parsedProjectData.audioItems[audioKey].voice.engineId != + undefined + ) + ) { + throw new Error('Every voice should have a "engineId" attribute.'); } // FIXME: assert engineId is registered if ( !parsedProjectData.audioKeys.every( (audioKey) => - parsedProjectData.audioItems[audioKey].styleId != undefined + parsedProjectData.audioItems[audioKey].voice.speakerId != + undefined ) ) { - throw new Error( - 'Every audioItem should have a "styleId" attribute.' - ); + throw new Error('Every voice should have a "speakerId" attribute.'); + } + if ( + !parsedProjectData.audioKeys.every( + (audioKey) => + parsedProjectData.audioItems[audioKey].voice.styleId != + undefined + ) + ) { + throw new Error('Every voice should have a "styleId" attribute.'); } if (confirm !== false && context.getters.IS_EDITED) { @@ -428,8 +478,11 @@ const morphingInfoSchema = z.object({ const audioItemSchema = z.object({ text: z.string(), - engineId: z.string().optional(), - styleId: z.number().optional(), + voice: z.object({ + engineId: z.string().uuid(), + speakerId: z.string().uuid(), + styleId: z.number(), + }), query: audioQuerySchema.optional(), presetKey: z.string().optional(), morphingInfo: morphingInfoSchema.optional(), diff --git a/src/store/type.ts b/src/store/type.ts index 1a1fecbd7e..508ad35b73 100644 --- a/src/store/type.ts +++ b/src/store/type.ts @@ -42,6 +42,7 @@ import { EngineSettings, MorphableTargetInfoTable, EngineSetting, + Voice, } from "@/type/preload"; import { IEngineConnectorFactory } from "@/infrastructures/EngineConnector"; import { QVueGlobals } from "quasar"; @@ -53,11 +54,9 @@ export type EditorAudioQuery = Omit & { outputSamplingRate: number | "engineDefault"; }; -// FIXME: SpeakerIdを追加する export type AudioItem = { text: string; - engineId?: string; - styleId?: number; + voice: Voice; query?: EditorAudioQuery; presetKey?: string; morphingInfo?: MorphingInfo; @@ -196,8 +195,7 @@ export type AudioStoreTypes = { GENERATE_AUDIO_ITEM: { action(payload: { text?: string; - engineId?: string; - styleId?: number; + voice?: Voice; presetKey?: string; baseAudioItem?: AudioItem; }): Promise; @@ -316,8 +314,8 @@ export type AudioStoreTypes = { }): Promise; }; - SET_AUDIO_STYLE_ID: { - mutation: { audioKey: string; engineId: string; styleId: number }; + SET_AUDIO_VOICE: { + mutation: { audioKey: string; voice: Voice }; }; SET_ACCENT_PHRASES: { @@ -505,17 +503,13 @@ export type AudioCommandStoreTypes = { action(payload: { audioKey: string; text: string }): void; }; - COMMAND_CHANGE_STYLE_ID: { - mutation: { engineId: string; styleId: number; audioKey: string } & ( + COMMAND_CHANGE_VOICE: { + mutation: { audioKey: string; voice: Voice } & ( | { update: "StyleId" } | { update: "AccentPhrases"; accentPhrases: AccentPhrase[] } | { update: "AudioQuery"; query: AudioQuery } ); - action(payload: { - audioKey: string; - engineId: string; - styleId: number; - }): void; + action(payload: { audioKey: string; voice: Voice }): void; }; COMMAND_CHANGE_ACCENT: { @@ -663,8 +657,7 @@ export type AudioCommandStoreTypes = { action(payload: { prevAudioKey: string; texts: string[]; - engineId: string; - styleId: number; + voice: Voice; }): string[]; }; }; diff --git a/src/views/EditorHome.vue b/src/views/EditorHome.vue index efc9d7d0cb..2ff98e6c20 100644 --- a/src/views/EditorHome.vue +++ b/src/views/EditorHome.vue @@ -199,6 +199,7 @@ import { HotkeyAction, HotkeyReturnType, SplitterPosition, + Voice, } from "@/type/preload"; import { parseCombo, setHotkeyFunctions } from "@/store/setting"; import cloneDeep from "clone-deep"; @@ -406,12 +407,10 @@ export default defineComponent({ ); const addAudioItem = async () => { const prevAudioKey = activeAudioKey.value; - let engineId: string | undefined = undefined; - let styleId: number | undefined = undefined; + let voice: Voice | undefined = undefined; let presetKey: string | undefined = undefined; if (prevAudioKey !== undefined) { - engineId = store.state.audioItems[prevAudioKey].engineId; - styleId = store.state.audioItems[prevAudioKey].styleId; + voice = store.state.audioItems[prevAudioKey].voice; presetKey = store.state.audioItems[prevAudioKey].presetKey; } let baseAudioItem: AudioItem | undefined = undefined; @@ -423,8 +422,7 @@ export default defineComponent({ //パラメータ引き継ぎがONの場合は話速等のパラメータを引き継いでテキスト欄を作成する //パラメータ引き継ぎがOFFの場合、baseAudioItemがundefinedになっているのでパラメータ引き継ぎは行われない const audioItem = await store.dispatch("GENERATE_AUDIO_ITEM", { - engineId, - styleId, + voice, presetKey, baseAudioItem, }); @@ -561,13 +559,11 @@ export default defineComponent({ focusCell({ audioKey: newAudioKey }); // 最初の話者を初期化 - if (audioItem.engineId != undefined && audioItem.styleId != undefined) { - store.dispatch("SETUP_SPEAKER", { - audioKey: newAudioKey, - engineId: audioItem.engineId, - styleId: audioItem.styleId, - }); - } + store.dispatch("SETUP_SPEAKER", { + audioKey: newAudioKey, + engineId: audioItem.voice.engineId, + styleId: audioItem.voice.styleId, + }); } // ショートカットキーの設定