diff --git a/src/background.ts b/src/background.ts index 7c9703e7bc..d3cc867275 100644 --- a/src/background.ts +++ b/src/background.ts @@ -32,6 +32,7 @@ import { defaultHotkeySettings, isMac, defaultToolbarButtonSetting, + engineSetting, } from "./type/preload"; import log from "electron-log"; @@ -131,9 +132,6 @@ const store = new Store({ } }, ">=0.14": (store) => { - if (store.get("savingSetting").outputSamplingRate == 24000) { - store.set("savingSetting.outputSamplingRate", "engineDefault"); - } // FIXME: できるならEngineManagerからEnginIDを取得したい const engineId = JSON.parse(process.env.DEFAULT_ENGINE_INFOS ?? "[]")[0] .uuid; @@ -148,6 +146,19 @@ const store = new Store({ defaultStyleId: defaultStyle.defaultStyleId, })) ); + + const outputSamplingRate: number = + // @ts-expect-error 削除されたパラメータ。 + store.get("savingSetting").outputSamplingRate; + store.set(`engineSettings.${engineId}`, { + useGpu: store.get("useGpu"), + outputSamplingRate: + outputSamplingRate === 24000 ? "engineDefault" : outputSamplingRate, + }); + // @ts-expect-error 削除されたパラメータ。 + store.delete("savingSetting.outputSamplingRate"); + // @ts-expect-error 削除されたパラメータ。 + store.delete("useGpu"); }, }, }); @@ -411,6 +422,22 @@ async function createWindow() { mainWindowState.manage(win); } +// UI処理を開始。その他の準備が完了した後に呼ばれる。 +async function start() { + const engineInfos = engineManager.fetchEngineInfos(); + const engineSettings = store.get("engineSettings"); + for (const engineInfo of engineInfos) { + if (!engineSettings[engineInfo.uuid]) { + // 空オブジェクトをパースさせることで、デフォルト値を取得する + engineSettings[engineInfo.uuid] = engineSetting.parse({}); + } + } + store.set("engineSettings", engineSettings); + + await createWindow(); + await engineManager.runEngineAll(win); +} + const menuTemplateForMac: Electron.MenuItemConstructorOptions[] = [ { label: "VOICEVOX", @@ -809,7 +836,7 @@ app.on("before-quit", async (event) => { appState.willRestart = false; appState.willQuit = false; - createWindow().then(() => engineManager.runEngineAll(win)); + start(); } else { log.info("Post engine kill process done. Now quit app"); } @@ -904,7 +931,7 @@ app.on("ready", async () => { await installVvppEngine(filePath); } - createWindow().then(() => engineManager.runEngineAll(win)); + start(); }); // 他のプロセスが起動したとき、`requestSingleInstanceLock`経由で`rawData`が送信される。 diff --git a/src/background/engineManager.ts b/src/background/engineManager.ts index 06107b092c..638aba52cd 100644 --- a/src/background/engineManager.ts +++ b/src/background/engineManager.ts @@ -233,8 +233,7 @@ export class EngineManager { const engineProcessContainer = this.engineProcessContainers[engineId]; engineProcessContainer.willQuitEngine = false; - const useGpu = this.store.get("useGpu"); - + const useGpu = this.store.get("engineSettings")[engineId].useGpu; log.info(`ENGINE ${engineId} mode: ${useGpu ? "GPU" : "CPU"}`); // エンジンプロセスの起動 diff --git a/src/components/SettingDialog.vue b/src/components/SettingDialog.vue index 4fe2f40c67..a8f08d683c 100644 --- a/src/components/SettingDialog.vue +++ b/src/components/SettingDialog.vue @@ -28,8 +28,19 @@
- +
エンジン
+
エンジンモード
@@ -51,28 +62,52 @@ - お使いのエンジンはCPU版のためGPUモードを利用できません。 + {{ + engineInfos[selectedEngineId].name + }}はCPU版のためGPUモードを利用できません。
+ +
音声のサンプリングレート
+
+ + + 再生・保存時の音声のサンプリングレートを変更します(サンプリングレートを上げても音声の品質は上がりません。) + + +
+ + + +
@@ -548,34 +583,6 @@ > - -
音声のサンプリングレート
-
- - - 再生・保存時の音声のサンプリングレートを変更します(サンプリングレートを上げても音声の品質は上がりません。) - - -
- - - -
@@ -694,6 +701,7 @@ import { useStore } from "@/store"; import { useQuasar } from "quasar"; import { SavingSetting, + EngineSetting, ExperimentalSetting, ActivePointScrollMode, SplitTextWhenPasteType, @@ -701,6 +709,8 @@ import { } from "@/type/preload"; import FileNamePatternDialog from "./FileNamePatternDialog.vue"; +type SamplingRateOption = EngineSetting["outputSamplingRate"]; + export default defineComponent({ name: "SettingDialog", @@ -724,16 +734,16 @@ export default defineComponent({ set: (val) => emit("update:modelValue", val), }); - const allEngineCanUseGPU = computed( - () => store.getters.ALL_ENGINE_CAN_USE_GPU - ); - - const engineMode = computed({ - get: () => (store.state.useGpu ? "switchGPU" : "switchCPU"), - set: (mode: string) => { - changeUseGPU(mode == "switchGPU" ? true : false); + const engineUseGpu = computed({ + get: () => { + return store.state.engineSettings[selectedEngineId.value].useGpu; + }, + set: (mode: boolean) => { + changeUseGpu(mode); }, }); + const engineIds = computed(() => store.state.engineIds); + const engineInfos = computed(() => store.state.engineInfos); const inheritAudioInfoMode = computed(() => store.state.inheritAudioInfo); const activePointScrollMode = computed({ get: () => store.state.activePointScrollMode, @@ -850,9 +860,7 @@ export default defineComponent({ }, }); - const changeUseGPU = async (useGpu: boolean) => { - if (store.state.useGpu === useGpu) return; - + const changeUseGpu = async (useGpu: boolean) => { $q.loading.show({ spinnerColor: "primary", spinnerSize: 50, @@ -860,7 +868,10 @@ export default defineComponent({ message: "起動モードを変更中です", }); - await store.dispatch("CHANGE_USE_GPU", { useGpu }); + await store.dispatch("CHANGE_USE_GPU", { + useGpu, + engineId: selectedEngineId.value, + }); $q.loading.hide(); }; @@ -879,13 +890,19 @@ export default defineComponent({ }); }; - const restartAllEngineProcess = () => { - store.dispatch("RESTART_ENGINES", { engineIds: store.state.engineIds }); - }; - const savingSetting = computed(() => store.state.savingSetting); - const samplingRateOptions: SavingSetting["outputSamplingRate"][] = [ + const engineUseGpuOptions = [ + { label: "CPU", value: false }, + { label: "GPU", value: true }, + ]; + + const gpuSwitchEnabled = (engineId: string) => { + // CPU版でもGPUモードからCPUモードに変更できるようにする + return store.getters.ENGINE_CAN_USE_GPU(engineId) || engineUseGpu.value; + }; + + const samplingRateOptions: SamplingRateOption[] = [ "engineDefault", 24000, 44100, @@ -893,11 +910,9 @@ export default defineComponent({ 88200, 96000, ]; - const renderSamplingRateLabel = ( - value: SavingSetting["outputSamplingRate"] - ) => { + const renderSamplingRateLabel = (value: SamplingRateOption): string => { if (value === "engineDefault") { - return "デフォルト"; + return "エンジンのデフォルト"; } else { return `${value / 1000} kHz`; } @@ -907,34 +922,58 @@ export default defineComponent({ key: keyof SavingSetting, data: string | boolean | number ) => { - const storeDispatch = (): void => { - store.dispatch("SET_SAVING_SETTING", { - data: { ...savingSetting.value, [key]: data }, - }); - }; - if (key === "outputSamplingRate" && data !== "engineDefault") { - $q.dialog({ - title: "出力サンプリングレートを変更します", - message: - "出力サンプリングレートを変更しても、音質は変化しません。また、音声の生成処理に若干時間がかかる場合があります。
変更しますか?", - html: true, - persistent: true, - ok: { - label: "変更する", - flat: true, - textColor: "display", - }, - cancel: { - label: "変更しない", - flat: true, - textColor: "display", - }, - }).onOk(storeDispatch); - return; - } - storeDispatch(); + store.dispatch("SET_SAVING_SETTING", { + data: { ...savingSetting.value, [key]: data }, + }); }; + const outputSamplingRate = computed({ + get: () => { + return store.state.engineSettings[selectedEngineId.value] + .outputSamplingRate; + }, + set: async (outputSamplingRate: SamplingRateOption) => { + if (outputSamplingRate !== "engineDefault") { + const confirmChange = await new Promise((resolve) => { + $q.dialog({ + title: "出力サンプリングレートを変更します", + message: + "出力サンプリングレートを変更しても、音質は変化しません。また、音声の生成処理に若干時間がかかる場合があります。
変更しますか?", + html: true, + persistent: true, + ok: { + label: "変更する", + flat: true, + textColor: "display", + }, + cancel: { + label: "変更しない", + flat: true, + textColor: "display", + }, + }) + .onOk(() => { + resolve(true); + }) + .onCancel(() => { + resolve(false); + }); + }); + if (!confirmChange) { + return; + } + } + + store.dispatch("SET_ENGINE_SETTING", { + engineId: selectedEngineId.value, + engineSetting: { + ...store.state.engineSettings[selectedEngineId.value], + outputSamplingRate, + }, + }); + }, + }); + const openFileExplore = async () => { const path = await window.electron.showOpenDirectoryDialog({ title: "書き出し先のフォルダを選択", @@ -960,10 +999,28 @@ export default defineComponent({ const showsFilePatternEditDialog = ref(false); + const selectedEngineIdRaw = ref(""); + const selectedEngineId = computed({ + get: () => { + return selectedEngineIdRaw.value || engineIds.value[0]; + }, + set: (engineId: string) => { + selectedEngineIdRaw.value = engineId; + }, + }); + const renderEngineNameLabel = (engineId: string) => { + return engineInfos.value[engineId].name; + }; + return { settingDialogOpenedComputed, - allEngineCanUseGPU, - engineMode, + engineUseGpu, + gpuSwitchEnabled, + engineIds, + engineInfos, + selectedEngineId, + engineUseGpuOptions, + renderEngineNameLabel, inheritAudioInfoMode, activePointScrollMode, activePointScrollModeOptions, @@ -972,11 +1029,11 @@ export default defineComponent({ availableAudioOutputDevices, changeinheritAudioInfo, changeExperimentalSetting, - restartAllEngineProcess, savingSetting, samplingRateOptions, renderSamplingRateLabel, handleSavingSettingChange, + outputSamplingRate, openFileExplore, currentThemeNameComputed, currentThemeComputed, @@ -1053,6 +1110,10 @@ export default defineComponent({ } } +.engine-setting { + align-items: flex-end; +} + .root { .scroller { overflow-y: scroll; diff --git a/src/store/audio.ts b/src/store/audio.ts index 7031a72198..b52130d3df 100644 --- a/src/store/audio.ts +++ b/src/store/audio.ts @@ -42,7 +42,10 @@ async function generateUniqueIdAndQuery( audioItem = JSON.parse(JSON.stringify(audioItem)) as AudioItem; const audioQuery = audioItem.query; if (audioQuery != undefined) { - audioQuery.outputSamplingRate = state.savingSetting.outputSamplingRate; + if (audioItem.engineId == undefined) + throw new Error(`audioItem.engineId is undefined`); + audioQuery.outputSamplingRate = + state.engineSettings[audioItem.engineId].outputSamplingRate; audioQuery.outputStereo = state.savingSetting.outputStereo; } diff --git a/src/store/engine.ts b/src/store/engine.ts index d9875cd009..5528c33abd 100644 --- a/src/store/engine.ts +++ b/src/store/engine.ts @@ -397,13 +397,4 @@ export const engineStore = createPartialStore({ return supportedDevices?.cuda || supportedDevices?.dml; }, }, - - // TODO:エンジン毎の設定が可能になれば消す - ALL_ENGINE_CAN_USE_GPU: { - getter: (state, getters) => { - return state.engineIds.every((engineId) => - getters.ENGINE_CAN_USE_GPU(engineId) - ); - }, - }, }); diff --git a/src/store/setting.ts b/src/store/setting.ts index 81259127fa..fd6f0ed887 100644 --- a/src/store/setting.ts +++ b/src/store/setting.ts @@ -27,7 +27,6 @@ export const settingStoreState: SettingStoreState = { exportLab: false, exportText: false, outputStereo: false, - outputSamplingRate: "engineDefault", audioOutputDevice: "default", }, hotkeySettings: [], @@ -55,6 +54,7 @@ export const settingStoreState: SettingStoreState = { confirmedTips: { tweakableSliderByScroll: false, }, + engineSettings: {}, }; export const settingStore = createPartialStore({ @@ -116,6 +116,15 @@ export const settingStore = createPartialStore({ commit("SET_CONFIRMED_TIPS", { confirmedTips: await window.electron.getSetting("confirmedTips"), }); + + for (const [engineId, engineSetting] of Object.entries( + await window.electron.getSetting("engineSettings") + )) { + commit("SET_ENGINE_SETTING", { + engineId, + engineSetting, + }); + } }, }, @@ -309,50 +318,66 @@ export const settingStore = createPartialStore({ }, }, + SET_ENGINE_SETTING: { + mutation(state, { engineSetting, engineId }) { + state.engineSettings[engineId] = engineSetting; + }, + async action({ commit, state }, { engineSetting, engineId }) { + window.electron.setSetting("engineSettings", { + ...state.engineSettings, + [engineId]: engineSetting, + }); + commit("SET_ENGINE_SETTING", { engineSetting, engineId }); + }, + }, + CHANGE_USE_GPU: { /** * CPU/GPUモードを切り替えようとする。 * GPUモードでエンジン起動に失敗した場合はCPUモードに戻す。 */ - action: createUILockAction(async ({ state, dispatch }, { useGpu }) => { - if (state.useGpu === useGpu) return; - - const isAvailableGPUMode = await window.electron.isAvailableGPUMode(); - - // 対応するGPUがない場合に変更を続行するか問う - if (useGpu && !isAvailableGPUMode) { - const result = await window.electron.showQuestionDialog({ - type: "warning", - title: "対応するGPUデバイスが見つかりません", - message: - "GPUモードの利用には対応するGPUデバイスが必要です。\n" + - "このままGPUモードに変更するとエンジンエラーが発生する可能性があります。本当に変更しますか?", - buttons: ["変更する", "変更しない"], - cancelId: 1, - }); - if (result == 1) { - return; + action: createUILockAction( + async ({ state, dispatch }, { useGpu, engineId }) => { + const isAvailableGPUMode = await window.electron.isAvailableGPUMode(); + + // 対応するGPUがない場合に変更を続行するか問う + if (useGpu && !isAvailableGPUMode) { + const result = await window.electron.showQuestionDialog({ + type: "warning", + title: "対応するGPUデバイスが見つかりません", + message: + "GPUモードの利用には対応するGPUデバイスが必要です。\n" + + "このままGPUモードに変更するとエンジンエラーが発生する可能性があります。本当に変更しますか?", + buttons: ["変更する", "変更しない"], + cancelId: 1, + }); + if (result == 1) { + return; + } } - } - await dispatch("SET_USE_GPU", { useGpu }); - const success = await dispatch("RESTART_ENGINES", { - engineIds: state.engineIds, - }); - - // GPUモードに変更できなかった場合はCPUモードに戻す - // FIXME: useGpu設定を保存してからエンジン起動を試すのではなく、逆にしたい - if (!success && useGpu) { - await window.electron.showMessageDialog({ - type: "error", - title: "GPUモードに変更できませんでした", - message: - "GPUモードでエンジンを起動できなかったためCPUモードに戻します", + await dispatch("SET_ENGINE_SETTING", { + engineSetting: { ...state.engineSettings[engineId], useGpu }, + engineId, + }); + const result = await dispatch("RESTART_ENGINES", { + engineIds: [engineId], }); - await dispatch("CHANGE_USE_GPU", { useGpu: false }); - return; + + // GPUモードに変更できなかった場合はCPUモードに戻す + // FIXME: useGpu設定を保存してからエンジン起動を試すのではなく、逆にしたい + if (!result.success && useGpu) { + await window.electron.showMessageDialog({ + type: "error", + title: "GPUモードに変更できませんでした", + message: + "GPUモードでエンジンを起動できなかったためCPUモードに戻します", + }); + await dispatch("CHANGE_USE_GPU", { useGpu: false, engineId }); + return; + } } - }), + ), }, }); diff --git a/src/store/type.ts b/src/store/type.ts index e0482b508c..ae556bdbcb 100644 --- a/src/store/type.ts +++ b/src/store/type.ts @@ -39,7 +39,9 @@ import { ConfirmedTips, EngineDirValidationResult, EditorFontType, + EngineSettings, MorphableTargetInfoTable, + EngineSetting, } from "@/type/preload"; import { IEngineConnectorFactory } from "@/infrastructures/EngineConnector"; import { QVueGlobals } from "quasar"; @@ -819,10 +821,6 @@ export type EngineStoreTypes = { ENGINE_CAN_USE_GPU: { getter: (engineId: string) => boolean; }; - - ALL_ENGINE_CAN_USE_GPU: { - getter: boolean; - }; }; /* @@ -977,6 +975,7 @@ export type SettingStoreState = { splitTextWhenPaste: SplitTextWhenPasteType; splitterPosition: SplitterPosition; confirmedTips: ConfirmedTips; + engineSettings: EngineSettings; }; export type SettingStoreTypes = { @@ -1041,8 +1040,16 @@ export type SettingStoreTypes = { action(payload: { confirmedTips: ConfirmedTips }): void; }; + SET_ENGINE_SETTING: { + mutation: { engineSetting: EngineSetting; engineId: string }; + action(payload: { + engineSetting: EngineSetting; + engineId: string; + }): Promise; + }; + CHANGE_USE_GPU: { - action(payload: { useGpu: boolean }): void; + action(payload: { useGpu: boolean; engineId: string }): Promise; }; }; @@ -1053,7 +1060,6 @@ export type SettingStoreTypes = { export type UiStoreState = { uiLockCount: number; dialogLockCount: number; - useGpu: boolean; inheritAudioInfo: boolean; activePointScrollMode: ActivePointScrollMode; isHelpDialogOpen: boolean; @@ -1148,11 +1154,6 @@ export type UiStoreTypes = { action(): void; }; - SET_USE_GPU: { - mutation: { useGpu: boolean }; - action(payload: { useGpu: boolean }): void; - }; - SET_INHERIT_AUDIOINFO: { mutation: { inheritAudioInfo: boolean }; action(payload: { inheritAudioInfo: boolean }): void; diff --git a/src/store/ui.ts b/src/store/ui.ts index c1870b42ea..6bbcc81176 100644 --- a/src/store/ui.ts +++ b/src/store/ui.ts @@ -36,7 +36,6 @@ export function withProgress( export const uiStoreState: UiStoreState = { uiLockCount: 0, dialogLockCount: 0, - useGpu: false, inheritAudioInfo: true, activePointScrollMode: "OFF", isHelpDialogOpen: false, @@ -173,10 +172,6 @@ export const uiStore = createPartialStore({ HYDRATE_UI_STORE: { async action({ commit }) { - commit("SET_USE_GPU", { - useGpu: await window.electron.getSetting("useGpu"), - }); - commit("SET_INHERIT_AUDIOINFO", { inheritAudioInfo: await window.electron.getSetting("inheritAudioInfo"), }); @@ -201,17 +196,6 @@ export const uiStore = createPartialStore({ }, }, - SET_USE_GPU: { - mutation(state, { useGpu }: { useGpu: boolean }) { - state.useGpu = useGpu; - }, - async action({ commit }, { useGpu }: { useGpu: boolean }) { - commit("SET_USE_GPU", { - useGpu: await window.electron.setSetting("useGpu", useGpu), - }); - }, - }, - SET_INHERIT_AUDIOINFO: { mutation(state, { inheritAudioInfo }: { inheritAudioInfo: boolean }) { state.inheritAudioInfo = inheritAudioInfo; diff --git a/src/type/preload.ts b/src/type/preload.ts index f2c9282165..c024a811db 100644 --- a/src/type/preload.ts +++ b/src/type/preload.ts @@ -255,10 +255,19 @@ export type SavingSetting = { avoidOverwrite: boolean; exportText: boolean; outputStereo: boolean; - outputSamplingRate: number | "engineDefault"; audioOutputDevice: string; }; +export type EngineSettings = Record; + +export const engineSetting = z.object({ + useGpu: z.boolean().default(false), + outputSamplingRate: z + .union([z.number(), z.literal("engineDefault")]) + .default("engineDefault"), +}); +export type EngineSetting = z.infer; + export type DefaultStyleId = { engineId: string; speakerUuid: string; @@ -433,7 +442,6 @@ export type ConfirmedTips = { }; export const electronStoreSchema = z .object({ - useGpu: z.boolean().default(false), inheritAudioInfo: z.boolean().default(true), activePointScrollMode: z .enum(["CONTINUOUSLY", "PAGE", "OFF"]) @@ -448,9 +456,6 @@ export const electronStoreSchema = z exportLab: z.boolean().default(false), exportText: z.boolean().default(false), outputStereo: z.boolean().default(false), - outputSamplingRate: z - .union([z.number(), z.literal("engineDefault")]) - .default("engineDefault"), audioOutputDevice: z.string().default(""), }) .passthrough() // 別のブランチでの開発中の設定項目があるコンフィグで死ぬのを防ぐ @@ -459,6 +464,7 @@ export const electronStoreSchema = z toolbarSetting: toolbarSettingSchema .array() .default(defaultToolbarButtonSetting), + engineSettings: z.record(engineSetting).default({}), userCharacterOrder: z.string().array().default([]), defaultStyleIds: z .object({ diff --git a/tests/unit/store/Vuex.spec.ts b/tests/unit/store/Vuex.spec.ts index d84e93f5b6..06e08bce5a 100644 --- a/tests/unit/store/Vuex.spec.ts +++ b/tests/unit/store/Vuex.spec.ts @@ -36,7 +36,6 @@ describe("store/vuex.js test", () => { nowPlayingContinuously: false, undoCommands: [], redoCommands: [], - useGpu: false, inheritAudioInfo: true, activePointScrollMode: "OFF", isHelpDialogOpen: false, @@ -61,9 +60,14 @@ describe("store/vuex.js test", () => { exportLab: false, exportText: false, outputStereo: false, - outputSamplingRate: 24000, audioOutputDevice: "default", }, + engineSettings: { + "88022f86-c823-436e-85a3-500c629749c4": { + outputSamplingRate: "engineDefault", + useGpu: false, + }, + }, themeSetting: { currentTheme: "Default", availableThemes: [], @@ -194,7 +198,6 @@ describe("store/vuex.js test", () => { assert.isEmpty(store.state.undoCommands); assert.isArray(store.state.redoCommands); assert.isEmpty(store.state.redoCommands); - assert.equal(store.state.useGpu, false); assert.equal(store.state.inheritAudioInfo, true); assert.equal(store.state.activePointScrollMode, "OFF"); assert.equal(store.state.isHelpDialogOpen, false);