From 123e55521ffa70abc66c9544927f1c9a73c3e03c Mon Sep 17 00:00:00 2001 From: Nanashi Date: Wed, 31 Jan 2024 15:46:41 +0900 Subject: [PATCH] =?UTF-8?q?Fix:=20=E3=82=A8=E3=83=B3=E3=82=B8=E3=83=B3?= =?UTF-8?q?=E3=81=8C=E8=B5=B7=E5=8B=95=E3=81=97=E3=81=A6=E3=81=84=E3=81=AA?= =?UTF-8?q?=E3=81=84=E3=81=A8=E7=94=BB=E9=9D=A2=E3=81=8C=E7=9C=9F=E3=81=A3?= =?UTF-8?q?=E7=99=BD=E3=81=AB=E3=81=AA=E3=82=8B=E3=81=AE=E3=82=92=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3=20(#1799)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix: エンジンが起動していないと画面が真っ白になるのを修正 * Fix: 型周りを修正 Co-Authored-By: y-chan * Fix: talk周りのハンドリングを修正 Co-Authored-By: y-chan * Add: テストを追加 * Fix: speakerUuidのインクリメントを修正 * `filterCharacterInfosByStyleType`をstoreでも使うようにする (#2) * Add: コメントを追加 Co-authored-by: Hiroshiba * Refactor: filterCharacterInfosByStyleTypeのテストを整理 * Code: コードのスタイルを修正 * テストコード削減 * ドキュメント調整 --------- Co-authored-by: y-chan Co-authored-by: Yuto Ashida Co-authored-by: Hiroshiba --- src/components/Talk/EditorHome.vue | 10 ++-- src/store/audio.ts | 32 ++++-------- src/store/utility.ts | 52 ++++++++++++++++++- tests/unit/store/utility.spec.ts | 80 +++++++++++++++++++++++++++++- 4 files changed, 146 insertions(+), 28 deletions(-) diff --git a/src/components/Talk/EditorHome.vue b/src/components/Talk/EditorHome.vue index 554882d58e..54e996276d 100644 --- a/src/components/Talk/EditorHome.vue +++ b/src/components/Talk/EditorHome.vue @@ -216,6 +216,7 @@ import { SplitterPositionType, Voice, } from "@/type/preload"; +import { filterCharacterInfosByStyleType } from "@/store/utility"; import { parseCombo, setHotkeyFunctions } from "@/store/setting"; const props = @@ -726,11 +727,10 @@ const isCharacterOrderDialogOpenComputed = computed({ // TODO: デフォルトスタイル選択(ソング)の実装 // デフォルトスタイル選択(トーク) const orderedTalkCharacterInfos = computed(() => { - const userOrderedCharacterInfos = - store.getters.USER_ORDERED_CHARACTER_INFOS("talk"); - if (userOrderedCharacterInfos == undefined) - throw new Error("userOrderedCharacterInfos == undefined"); - return userOrderedCharacterInfos; + return filterCharacterInfosByStyleType( + store.getters.GET_ORDERED_ALL_CHARACTER_INFOS, + "talk" + ); }); const isDefaultStyleSelectDialogOpenComputed = computed({ get: () => diff --git a/src/store/audio.ts b/src/store/audio.ts index e2ef419630..e889f13186 100644 --- a/src/store/audio.ts +++ b/src/store/audio.ts @@ -26,6 +26,7 @@ import { DEFAULT_STYLE_NAME, formatCharacterStyleName, TuningTranscription, + filterCharacterInfosByStyleType, } from "./utility"; import { convertAudioQueryFromEditorToEngine } from "./proxy"; import { createPartialStore } from "./vuex"; @@ -476,30 +477,19 @@ export const audioStore = createPartialStore({ * `singerLike`の場合はhummingかsingなスタイルのみを返す。 */ getter: (state, getters) => (styleType: "all" | "singerLike" | "talk") => { - const isSingingStyle = (styleInfo: StyleInfo) => { - return ( - styleInfo.styleType === "humming" || styleInfo.styleType === "sing" - ); - }; - const allCharacterInfos = getters.GET_ALL_CHARACTER_INFOS; if (allCharacterInfos.size === 0) return undefined; + + let flattenCharacterInfos = [...allCharacterInfos.values()]; + // "all"以外の場合は、スタイル・キャラクターをフィルタリングする + if (styleType !== "all") { + flattenCharacterInfos = filterCharacterInfosByStyleType( + flattenCharacterInfos, + styleType + ); + } return ( - [...allCharacterInfos.values()] - // スタイルタイプでフィルタリング - .map((info) => { - info.metas.styles = info.metas.styles.filter((style) => { - const isSinging = isSingingStyle(style); - return ( - styleType === "all" || - (styleType === "singerLike" && isSinging) || - (styleType === "talk" && !isSinging) - ); - }); - return info; - }) - // スタイルがなくなったキャラクターを除外 - .filter((info) => info.metas.styles.length !== 0) + flattenCharacterInfos // ユーザーが並び替えた順番に並び替え .sort( (a, b) => diff --git a/src/store/utility.ts b/src/store/utility.ts index 6d1d0385de..458d0dafee 100644 --- a/src/store/utility.ts +++ b/src/store/utility.ts @@ -1,7 +1,13 @@ import path from "path"; import { Platform } from "quasar"; import { diffArrays } from "diff"; -import { ToolbarButtonTagType, isMac } from "@/type/preload"; +import { + CharacterInfo, + StyleInfo, + StyleType, + ToolbarButtonTagType, + isMac, +} from "@/type/preload"; import { AccentPhrase, Mora } from "@/openapi"; export const DEFAULT_STYLE_NAME = "ノーマル"; @@ -478,3 +484,47 @@ export const isOnCommandOrCtrlKeyDown = (event: { metaKey: boolean; ctrlKey: boolean; }) => (isMac && event.metaKey) || (!isMac && event.ctrlKey); + +/** + * スタイルがシングエディタで利用可能なスタイルかどうかを判定します。 + */ +export const isSingingStyle = (styleInfo: StyleInfo) => { + return ( + styleInfo.styleType === "humming" || + styleInfo.styleType === "sing" || + styleInfo.styleType === "sing_teacher" + ); +}; + +/** + * CharacterInfoの配列を、指定されたスタイルタイプでフィルタリングします。 + * singerLikeはソング系スタイルのみを残します。 + * talkはソング系スタイルをすべて除外します。 + * FIXME: 上記以外のフィルタリング機能はテストでしか使っていないので、しばらくそのままなら削除する + */ +export const filterCharacterInfosByStyleType = ( + characterInfos: CharacterInfo[], + styleType: StyleType | "singerLike" +): CharacterInfo[] => { + const withStylesFiltered: CharacterInfo[] = characterInfos.map( + (characterInfo) => { + const styles = characterInfo.metas.styles.filter((styleInfo) => { + if (styleType === "singerLike") { + return isSingingStyle(styleInfo); + } + // 過去のエンジンにはstyleTypeが存在しないので、「singerLike以外」をtalkとして扱っている。 + if (styleType === "talk") { + return !isSingingStyle(styleInfo); + } + return styleInfo.styleType === styleType; + }); + return { ...characterInfo, metas: { ...characterInfo.metas, styles } }; + } + ); + + const withoutEmptyStyles = withStylesFiltered.filter( + (characterInfo) => characterInfo.metas.styles.length > 0 + ); + + return withoutEmptyStyles; +}; diff --git a/tests/unit/store/utility.spec.ts b/tests/unit/store/utility.spec.ts index f7d20bee6b..7148d15e74 100644 --- a/tests/unit/store/utility.spec.ts +++ b/tests/unit/store/utility.spec.ts @@ -1,5 +1,14 @@ +import { v4 as uuidv4 } from "uuid"; import { AccentPhrase, Mora } from "@/openapi"; -import { ToolbarButtonTagType, isMac } from "@/type/preload"; +import { + CharacterInfo, + StyleInfo, + EngineId, + SpeakerId, + StyleId, + ToolbarButtonTagType, + isMac, +} from "@/type/preload"; import { formatCharacterStyleName, sanitizeFileName, @@ -16,6 +25,7 @@ import { convertLongVowel, getBaseName, isOnCommandOrCtrlKeyDown, + filterCharacterInfosByStyleType, } from "@/store/utility"; function createDummyMora(text: string): Mora { @@ -319,3 +329,71 @@ test("isOnCommandOrCtrlKeyDown", () => { false ); }); + +describe("filterCharacterInfosByStyleType", () => { + const createCharacterInfo = ( + styleTypes: (undefined | "talk" | "humming" | "sing")[] + ): CharacterInfo => { + const engineId = EngineId(uuidv4()); + return { + portraitPath: "path/to/portrait", + metas: { + policy: "policy", + speakerName: "speakerName", + speakerUuid: SpeakerId(uuidv4()), + styles: styleTypes.map((styleType) => ({ + styleType, + styleName: "styleName", + engineId, + styleId: StyleId(Math.random()), + iconPath: "path/to/icon", + portraitPath: "path/to/portrait", + voiceSamplePaths: [], + })), + }, + }; + }; + const characterInfos: CharacterInfo[] = [ + createCharacterInfo(["talk"]), + createCharacterInfo(["humming"]), + createCharacterInfo(["sing"]), + createCharacterInfo(["talk", "humming", "sing"]), + createCharacterInfo([undefined]), + ]; + + for (const styleType of ["humming", "sing"] as const) { + test(`${styleType}のキャラクターが取得できる`, () => { + const filtered = filterCharacterInfosByStyleType( + characterInfos, + styleType + ); + // talkしかないキャラクターは除外される + expect(filtered.length).toBe(2); + filtered.forEach((c) => { + // styleTypeが指定したものになっている + expect(c.metas.styles[0].styleType).toBe(styleType); + // stylesの数が正しい + expect(c.metas.styles.length).toBe(1); + }); + }); + } + + test(`singerLikeを指定するとsingとhummingのキャラクターが取得できる`, () => { + const filtered = filterCharacterInfosByStyleType( + characterInfos, + "singerLike" + ); + expect(filtered.length).toBe(3); + expect(filtered[0].metas.styles.length).toBe(1); + expect(filtered[1].metas.styles.length).toBe(1); + expect(filtered[2].metas.styles.length).toBe(2); + }); + + test(`talkを指定するとsingerLike以外のキャラクターが取得できる`, () => { + const filtered = filterCharacterInfosByStyleType(characterInfos, "talk"); + expect(filtered.length).toBe(3); + expect(filtered[0].metas.styles.length).toBe(1); + expect(filtered[1].metas.styles.length).toBe(1); + expect(filtered[2].metas.styles.length).toBe(1); + }); +});