From f743e45ee6a60c68e19b372c9a374df68f7ee7d6 Mon Sep 17 00:00:00 2001 From: X-20A <155217226+X-20A@users.noreply.github.com> Date: Sat, 16 Nov 2024 08:23:17 +0900 Subject: [PATCH 01/11] =?UTF-8?q?=E3=81=A8=E3=82=8A=E3=81=82=E3=81=88?= =?UTF-8?q?=E3=81=9A=E5=8B=95=E3=81=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/App.vue | 10 ++++++++-- src/store/setting.ts | 2 ++ src/store/type.ts | 5 +++++ src/store/ui.ts | 17 ++++++++++++++++- src/type/preload.ts | 2 ++ 5 files changed, 33 insertions(+), 3 deletions(-) diff --git a/src/components/App.vue b/src/components/App.vue index b4798dead7..511de42abf 100644 --- a/src/components/App.vue +++ b/src/components/App.vue @@ -28,7 +28,7 @@ import { useGtm } from "@gtm-support/vue-gtm"; import { TooltipProvider } from "radix-vue"; import TalkEditor from "@/components/Talk/TalkEditor.vue"; import SingEditor from "@/components/Sing/SingEditor.vue"; -import { EngineId } from "@/type/preload"; +import { EngineId, EditorType } from "@/type/preload"; import ErrorBoundary from "@/components/ErrorBoundary.vue"; import { useStore } from "@/store"; import { useHotkeyManager } from "@/plugins/hotkeyPlugin"; @@ -77,6 +77,10 @@ watch( async (openedEditor) => { if (openedEditor != undefined) { hotkeyManager.onEditorChange(openedEditor); + await store.dispatch("SET_ROOT_MISC_SETTING", { + key: "editorType", + value: openedEditor, + }); } }, ); @@ -111,7 +115,9 @@ onMounted(async () => { const projectFilePath = urlParams.get("projectFilePath"); // どちらのエディタを開くか設定 - await store.actions.SET_OPENED_EDITOR({ editor: "talk" }); + await store.actions.SET_OPENED_EDITOR({ + editor: store.state.editorType as EditorType, + }); // ショートカットキーの設定を登録 const hotkeySettings = store.state.hotkeySettings; diff --git a/src/store/setting.ts b/src/store/setting.ts index 7567a96eb7..e3e5da79d9 100644 --- a/src/store/setting.ts +++ b/src/store/setting.ts @@ -71,6 +71,7 @@ export const settingStoreState: SettingStoreState = { }, showSingCharacterPortrait: true, playheadPositionDisplayFormat: "MINUTES_SECONDS", + editorType: "talk", }; export const settingStore = createPartialStore({ @@ -148,6 +149,7 @@ export const settingStore = createPartialStore({ "undoableTrackOperations", "showSingCharacterPortrait", "playheadPositionDisplayFormat", + "editorType", ] as const; // rootMiscSettingKeysに値を足し忘れていたときに型エラーを出す検出用コード diff --git a/src/store/type.ts b/src/store/type.ts index eebfd77fcc..f8a5684653 100644 --- a/src/store/type.ts +++ b/src/store/type.ts @@ -2053,6 +2053,11 @@ export type UiStoreTypes = { action(payload: { activePointScrollMode: ActivePointScrollMode }): void; }; + SET_EDITOR_TYPE: { + mutation: { editorType: EditorType }; + action(payload: { editorType: EditorType }): void; + }; + SET_AVAILABLE_THEMES: { mutation: { themes: ThemeConf[] }; }; diff --git a/src/store/ui.ts b/src/store/ui.ts index 8eebacd042..02c248e6d7 100644 --- a/src/store/ui.ts +++ b/src/store/ui.ts @@ -12,7 +12,7 @@ import { UiStoreTypes, } from "./type"; import { createPartialStore } from "./vuex"; -import { ActivePointScrollMode } from "@/type/preload"; +import { ActivePointScrollMode, EditorType } from "@/type/preload"; import { AlertDialogOptions, ConfirmDialogOptions, @@ -260,6 +260,10 @@ export const uiStore = createPartialStore({ ), }); + mutations.SET_OPENED_EDITOR({ + editor: await window.backend.getSetting("editorType"), + }); + // electron-window-stateがvuex初期化前に働くので // ここで改めてelectron windowの最大化状態をVuex storeに同期 if (await window.backend.isMaximizedWindow()) { @@ -335,6 +339,17 @@ export const uiStore = createPartialStore({ }, }, + SET_EDITOR_TYPE: { + mutation(state, { editorType }: { editorType: EditorType }) { + state.editorType = editorType; + }, + async action({ mutations }, { editorType }: { editorType: EditorType }) { + mutations.SET_OPENED_EDITOR({ + editor: await window.backend.setSetting("editorType", editorType), + }); + }, + }, + /** * 選択可能なテーマをセットする。 * NOTE: カスタムテーマが導入された場合を見越して残している。 diff --git a/src/type/preload.ts b/src/type/preload.ts index 7e6d141999..753ea0927c 100644 --- a/src/type/preload.ts +++ b/src/type/preload.ts @@ -594,6 +594,7 @@ export const rootMiscSettingSchema = z.object({ playheadPositionDisplayFormat: z .enum(["MINUTES_SECONDS", "MEASURES_BEATS"]) .default("MINUTES_SECONDS"), // 再生ヘッド位置の表示モード + editorType: z.enum(["talk", "song"]).default("talk"), }); export type RootMiscSettingType = z.infer; @@ -603,6 +604,7 @@ export const configSchema = z activePointScrollMode: z .enum(["CONTINUOUSLY", "PAGE", "OFF"]) .default("OFF"), + editorType: z.enum(["talk", "song"]).default("talk"), savingSetting: z .object({ fileEncoding: z.enum(["UTF-8", "Shift_JIS"]).default("UTF-8"), From 093c6d2fff95475b198080ac7a36313c49d1615d Mon Sep 17 00:00:00 2001 From: X-20A <155217226+X-20A@users.noreply.github.com> Date: Sat, 16 Nov 2024 22:47:05 +0900 Subject: [PATCH 02/11] =?UTF-8?q?=E5=9E=8B=E8=BF=BD=E8=A8=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/App.vue | 4 ++-- src/store/type.ts | 1 + src/store/ui.ts | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/App.vue b/src/components/App.vue index 511de42abf..faffb91753 100644 --- a/src/components/App.vue +++ b/src/components/App.vue @@ -28,7 +28,7 @@ import { useGtm } from "@gtm-support/vue-gtm"; import { TooltipProvider } from "radix-vue"; import TalkEditor from "@/components/Talk/TalkEditor.vue"; import SingEditor from "@/components/Sing/SingEditor.vue"; -import { EngineId, EditorType } from "@/type/preload"; +import { EngineId } from "@/type/preload"; import ErrorBoundary from "@/components/ErrorBoundary.vue"; import { useStore } from "@/store"; import { useHotkeyManager } from "@/plugins/hotkeyPlugin"; @@ -116,7 +116,7 @@ onMounted(async () => { // どちらのエディタを開くか設定 await store.actions.SET_OPENED_EDITOR({ - editor: store.state.editorType as EditorType, + editor: store.state.editorType, }); // ショートカットキーの設定を登録 diff --git a/src/store/type.ts b/src/store/type.ts index f8a5684653..99302fc540 100644 --- a/src/store/type.ts +++ b/src/store/type.ts @@ -1921,6 +1921,7 @@ export type SettingStoreTypes = { */ export type UiStoreState = { + editorType: EditorType; openedEditor: EditorType | undefined; // undefinedのときはどのエディタを開くか定まっていない uiLockCount: number; dialogLockCount: number; diff --git a/src/store/ui.ts b/src/store/ui.ts index 02c248e6d7..81f704aac8 100644 --- a/src/store/ui.ts +++ b/src/store/ui.ts @@ -63,6 +63,7 @@ export function withProgress( } export const uiStoreState: UiStoreState = { + editorType: "talk", openedEditor: undefined, uiLockCount: 0, dialogLockCount: 0, From 98ca600f3bff8131c1473a7569573af02a0b879a Mon Sep 17 00:00:00 2001 From: X-20A <155217226+X-20A@users.noreply.github.com> Date: Mon, 18 Nov 2024 07:18:37 +0900 Subject: [PATCH 03/11] =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/App.vue | 9 ------ .../Menu/MenuBar/TitleBarEditorSwitcher.vue | 5 +++- src/store/setting.ts | 4 +-- src/store/type.ts | 12 -------- src/store/ui.ts | 28 +------------------ src/type/preload.ts | 3 +- 6 files changed, 8 insertions(+), 53 deletions(-) diff --git a/src/components/App.vue b/src/components/App.vue index faffb91753..2ff6753331 100644 --- a/src/components/App.vue +++ b/src/components/App.vue @@ -77,10 +77,6 @@ watch( async (openedEditor) => { if (openedEditor != undefined) { hotkeyManager.onEditorChange(openedEditor); - await store.dispatch("SET_ROOT_MISC_SETTING", { - key: "editorType", - value: openedEditor, - }); } }, ); @@ -114,11 +110,6 @@ onMounted(async () => { // プロジェクトファイルのパスを取得 const projectFilePath = urlParams.get("projectFilePath"); - // どちらのエディタを開くか設定 - await store.actions.SET_OPENED_EDITOR({ - editor: store.state.editorType, - }); - // ショートカットキーの設定を登録 const hotkeySettings = store.state.hotkeySettings; hotkeyManager.load(structuredClone(toRaw(hotkeySettings))); diff --git a/src/components/Menu/MenuBar/TitleBarEditorSwitcher.vue b/src/components/Menu/MenuBar/TitleBarEditorSwitcher.vue index 6410cdae55..14e217e25f 100644 --- a/src/components/Menu/MenuBar/TitleBarEditorSwitcher.vue +++ b/src/components/Menu/MenuBar/TitleBarEditorSwitcher.vue @@ -30,7 +30,10 @@ const openedEditor = computed(() => store.state.openedEditor); const uiLocked = computed(() => store.getters.UI_LOCKED); const switchEditor = async (editor: EditorType) => { - await store.actions.SET_OPENED_EDITOR({ editor }); + await store.dispatch("SET_ROOT_MISC_SETTING", { + key: "openedEditor", + value: editor, + }); }; diff --git a/src/store/setting.ts b/src/store/setting.ts index e3e5da79d9..2d149560cd 100644 --- a/src/store/setting.ts +++ b/src/store/setting.ts @@ -71,7 +71,7 @@ export const settingStoreState: SettingStoreState = { }, showSingCharacterPortrait: true, playheadPositionDisplayFormat: "MINUTES_SECONDS", - editorType: "talk", + openedEditor: "talk", }; export const settingStore = createPartialStore({ @@ -149,7 +149,7 @@ export const settingStore = createPartialStore({ "undoableTrackOperations", "showSingCharacterPortrait", "playheadPositionDisplayFormat", - "editorType", + "openedEditor", ] as const; // rootMiscSettingKeysに値を足し忘れていたときに型エラーを出す検出用コード diff --git a/src/store/type.ts b/src/store/type.ts index 99302fc540..cfe438912f 100644 --- a/src/store/type.ts +++ b/src/store/type.ts @@ -1921,8 +1921,6 @@ export type SettingStoreTypes = { */ export type UiStoreState = { - editorType: EditorType; - openedEditor: EditorType | undefined; // undefinedのときはどのエディタを開くか定まっていない uiLockCount: number; dialogLockCount: number; reloadingLock: boolean; @@ -1952,11 +1950,6 @@ export type DialogStates = { }; export type UiStoreTypes = { - SET_OPENED_EDITOR: { - mutation: { editor: EditorType }; - action(palyoad: { editor: EditorType }): void; - }; - UI_LOCKED: { getter: boolean; }; @@ -2054,11 +2047,6 @@ export type UiStoreTypes = { action(payload: { activePointScrollMode: ActivePointScrollMode }): void; }; - SET_EDITOR_TYPE: { - mutation: { editorType: EditorType }; - action(payload: { editorType: EditorType }): void; - }; - SET_AVAILABLE_THEMES: { mutation: { themes: ThemeConf[] }; }; diff --git a/src/store/ui.ts b/src/store/ui.ts index 81f704aac8..aec24a5b47 100644 --- a/src/store/ui.ts +++ b/src/store/ui.ts @@ -12,7 +12,7 @@ import { UiStoreTypes, } from "./type"; import { createPartialStore } from "./vuex"; -import { ActivePointScrollMode, EditorType } from "@/type/preload"; +import { ActivePointScrollMode } from "@/type/preload"; import { AlertDialogOptions, ConfirmDialogOptions, @@ -63,8 +63,6 @@ export function withProgress( } export const uiStoreState: UiStoreState = { - editorType: "talk", - openedEditor: undefined, uiLockCount: 0, dialogLockCount: 0, reloadingLock: false, @@ -91,15 +89,6 @@ export const uiStoreState: UiStoreState = { }; export const uiStore = createPartialStore({ - SET_OPENED_EDITOR: { - mutation(state, { editor }) { - state.openedEditor = editor; - }, - action({ mutations }, { editor }) { - mutations.SET_OPENED_EDITOR({ editor }); - }, - }, - UI_LOCKED: { getter(state) { return state.uiLockCount > 0; @@ -261,10 +250,6 @@ export const uiStore = createPartialStore({ ), }); - mutations.SET_OPENED_EDITOR({ - editor: await window.backend.getSetting("editorType"), - }); - // electron-window-stateがvuex初期化前に働くので // ここで改めてelectron windowの最大化状態をVuex storeに同期 if (await window.backend.isMaximizedWindow()) { @@ -340,17 +325,6 @@ export const uiStore = createPartialStore({ }, }, - SET_EDITOR_TYPE: { - mutation(state, { editorType }: { editorType: EditorType }) { - state.editorType = editorType; - }, - async action({ mutations }, { editorType }: { editorType: EditorType }) { - mutations.SET_OPENED_EDITOR({ - editor: await window.backend.setSetting("editorType", editorType), - }); - }, - }, - /** * 選択可能なテーマをセットする。 * NOTE: カスタムテーマが導入された場合を見越して残している。 diff --git a/src/type/preload.ts b/src/type/preload.ts index 753ea0927c..11faf381ec 100644 --- a/src/type/preload.ts +++ b/src/type/preload.ts @@ -594,7 +594,7 @@ export const rootMiscSettingSchema = z.object({ playheadPositionDisplayFormat: z .enum(["MINUTES_SECONDS", "MEASURES_BEATS"]) .default("MINUTES_SECONDS"), // 再生ヘッド位置の表示モード - editorType: z.enum(["talk", "song"]).default("talk"), + openedEditor: z.enum(["talk", "song"]).default("talk"), }); export type RootMiscSettingType = z.infer; @@ -604,7 +604,6 @@ export const configSchema = z activePointScrollMode: z .enum(["CONTINUOUSLY", "PAGE", "OFF"]) .default("OFF"), - editorType: z.enum(["talk", "song"]).default("talk"), savingSetting: z .object({ fileEncoding: z.enum(["UTF-8", "Shift_JIS"]).default("UTF-8"), From 0697dc6433b6aa6fed03249ccaa9b89d3b6f5f21 Mon Sep 17 00:00:00 2001 From: Hiroshiba Date: Mon, 18 Nov 2024 16:34:07 +0900 Subject: [PATCH 04/11] =?UTF-8?q?watchEffect=E3=82=92=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=E3=81=97=E3=81=A6=E3=82=A8=E3=83=87=E3=82=A3=E3=82=BF=E3=81=AE?= =?UTF-8?q?=E5=88=87=E3=82=8A=E6=9B=BF=E3=81=88=E3=82=92=E7=9B=A3=E8=A6=96?= =?UTF-8?q?=E3=81=97=E3=80=81=E3=82=B7=E3=83=A7=E3=83=BC=E3=83=88=E3=82=AB?= =?UTF-8?q?=E3=83=83=E3=83=88=E3=82=AD=E3=83=BC=E3=81=AE=E8=A8=AD=E5=AE=9A?= =?UTF-8?q?=E3=82=92=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/App.vue | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/components/App.vue b/src/components/App.vue index 2ff6753331..1e1f9253ba 100644 --- a/src/components/App.vue +++ b/src/components/App.vue @@ -72,13 +72,11 @@ watchEffect(() => { }); // エディタの切り替えを監視してショートカットキーの設定を変更する -watch( - () => store.state.openedEditor, - async (openedEditor) => { - if (openedEditor != undefined) { - hotkeyManager.onEditorChange(openedEditor); - } +watchEffect( + () => { + hotkeyManager.onEditorChange(openedEditor.value); }, + { flush: "post" }, ); // テーマの変更を監視してCSS変数を変更する From ed3d4ab6929db2118d28783126f623cfad1addd2 Mon Sep 17 00:00:00 2001 From: Hiroshiba Date: Tue, 19 Nov 2024 00:22:27 +0900 Subject: [PATCH 05/11] =?UTF-8?q?openedEditor=E3=82=92undefined=E3=81=A7?= =?UTF-8?q?=E5=88=9D=E6=9C=9F=E5=8C=96=E3=81=97=E3=80=81=E8=A8=AD=E5=AE=9A?= =?UTF-8?q?=E3=82=B9=E3=82=AD=E3=83=BC=E3=83=9E=E3=82=92=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/store/setting.ts | 2 +- src/store/type.ts | 4 +++- src/type/preload.ts | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/store/setting.ts b/src/store/setting.ts index 2d149560cd..ba9c53548d 100644 --- a/src/store/setting.ts +++ b/src/store/setting.ts @@ -18,6 +18,7 @@ import { import { IsEqual } from "@/type/utility"; export const settingStoreState: SettingStoreState = { + openedEditor: undefined, savingSetting: { fileEncoding: "UTF-8", fileNamePattern: "", @@ -71,7 +72,6 @@ export const settingStoreState: SettingStoreState = { }, showSingCharacterPortrait: true, playheadPositionDisplayFormat: "MINUTES_SECONDS", - openedEditor: "talk", }; export const settingStore = createPartialStore({ diff --git a/src/store/type.ts b/src/store/type.ts index cfe438912f..fae93613da 100644 --- a/src/store/type.ts +++ b/src/store/type.ts @@ -1825,7 +1825,9 @@ export type SettingStoreState = { experimentalSetting: ExperimentalSettingType; confirmedTips: ConfirmedTips; engineSettings: EngineSettings; -} & RootMiscSettingType; +} & Omit & { + openedEditor: EditorType | undefined; // undefinedのときはどのエディタを開くか定まっていない + }; // keyとvalueの型を連動するようにしたPayloadを作る type KeyValuePayload = K extends keyof R diff --git a/src/type/preload.ts b/src/type/preload.ts index 11faf381ec..8ded07113e 100644 --- a/src/type/preload.ts +++ b/src/type/preload.ts @@ -571,6 +571,7 @@ export type ConfirmedTips = { // ルート直下にある雑多な設定値 export const rootMiscSettingSchema = z.object({ + openedEditor: z.enum(["talk", "song"]).default("talk"), editorFont: z.enum(["default", "os"]).default("default"), showTextLineNumber: z.boolean().default(false), showAddAudioItemButton: z.boolean().default(true), @@ -594,7 +595,6 @@ export const rootMiscSettingSchema = z.object({ playheadPositionDisplayFormat: z .enum(["MINUTES_SECONDS", "MEASURES_BEATS"]) .default("MINUTES_SECONDS"), // 再生ヘッド位置の表示モード - openedEditor: z.enum(["talk", "song"]).default("talk"), }); export type RootMiscSettingType = z.infer; From 6f9163286f480a649e44ed14a9aced0e6e4fda0c Mon Sep 17 00:00:00 2001 From: X-20A <155217226+X-20A@users.noreply.github.com> Date: Tue, 19 Nov 2024 13:06:37 +0900 Subject: [PATCH 06/11] =?UTF-8?q?=E5=BE=AE=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/App.vue | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/App.vue b/src/components/App.vue index 1e1f9253ba..e1cc5c9a42 100644 --- a/src/components/App.vue +++ b/src/components/App.vue @@ -74,7 +74,9 @@ watchEffect(() => { // エディタの切り替えを監視してショートカットキーの設定を変更する watchEffect( () => { - hotkeyManager.onEditorChange(openedEditor.value); + if (openedEditor.value) { + hotkeyManager.onEditorChange(openedEditor.value); + } }, { flush: "post" }, ); From e9cfec36f78f81a54b86d5e9bb184c9350d1d999 Mon Sep 17 00:00:00 2001 From: X-20A <155217226+X-20A@users.noreply.github.com> Date: Fri, 22 Nov 2024 12:40:48 +0900 Subject: [PATCH 07/11] wip --- src/sing/audioRendering.ts | 37 ++++++++++++++++++++++++++++++++++--- src/store/singing.ts | 11 ++++++++--- src/type/globals.d.ts | 5 +++++ 3 files changed, 47 insertions(+), 6 deletions(-) diff --git a/src/sing/audioRendering.ts b/src/sing/audioRendering.ts index c96ef4d531..1414dfd8ba 100644 --- a/src/sing/audioRendering.ts +++ b/src/sing/audioRendering.ts @@ -4,6 +4,7 @@ import { linearToDecibel, } from "@/sing/domain"; import { Timer } from "@/sing/utility"; +import { showAlertDialog } from "@/components/Dialog/Dialog"; const getEarliestSchedulableContextTime = (audioContext: BaseAudioContext) => { const renderQuantumSize = 128; @@ -196,11 +197,26 @@ export class Transport { /** * 再生を開始します。すでに再生中の場合は何も行いません。 + * @param device 再生させるデバイスのID、指定が無ければデフォルトで再生 */ - start() { + start(device?: string) { if (this._state === "started") return; const contextTime = this.audioContext.currentTime; + device = device ? device : ""; + if (this.audioContext.setSinkId) { + this.audioContext + .setSinkId(device === "default" ? "" : device) + .catch((err: unknown) => { + void showAlertDialog({ + type: "error", + title: "エラー", + message: "再生デバイスが見つかりません", + }); + throw err; + }); + } + this._state = "started"; this.startContextTime = contextTime; @@ -766,7 +782,7 @@ export type PolySynthOptions = { * ポリフォニックシンセサイザーです。 */ export class PolySynth implements Instrument { - private readonly audioContext: BaseAudioContext; + private readonly audioContext: AudioContext; private readonly gainNode: GainNode; private readonly oscParams: SynthOscParams; private readonly filterParams: SynthFilterParams; @@ -778,7 +794,7 @@ export class PolySynth implements Instrument { return this.gainNode; } - constructor(audioContext: BaseAudioContext, options?: PolySynthOptions) { + constructor(audioContext: AudioContext, options?: PolySynthOptions) { this.audioContext = audioContext; this.oscParams = options?.osc ?? { type: "square", @@ -806,12 +822,27 @@ export class PolySynth implements Instrument { * @param contextTime ノートオンを行う時刻(コンテキスト時刻) * @param noteNumber MIDIノート番号 * @param duration ノートの長さ(秒) + * @param device 再生させるデバイスのID、指定が無ければデフォルトで再生 */ noteOn( contextTime: number | "immediately", noteNumber: number, duration?: number, + device?: string, ) { + device = device ? device : ""; + if (this.audioContext.setSinkId) { + this.audioContext + .setSinkId(device === "default" ? "" : device) + .catch((err: unknown) => { + void showAlertDialog({ + type: "error", + title: "エラー", + message: "再生デバイスが見つかりません", + }); + throw err; + }); + } let voice = this.voices.find((value) => { return value.isActive && value.noteNumber === noteNumber; }); diff --git a/src/store/singing.ts b/src/store/singing.ts index f558d24b01..999bee4569 100644 --- a/src/store/singing.ts +++ b/src/store/singing.ts @@ -1482,7 +1482,7 @@ export const singingStore = createPartialStore({ } mutations.SET_PLAYBACK_STATE({ nowPlaying: true }); - transport.start(); + transport.start(state.savingSetting.audioOutputDevice); animationTimer.start(() => { playheadPosition.value = getters.SECOND_TO_TICK(transport.time); }); @@ -1521,7 +1521,7 @@ export const singingStore = createPartialStore({ PLAY_PREVIEW_SOUND: { async action( - _, + { state }, { noteNumber, duration }: { noteNumber: number; duration?: number }, ) { if (!audioContext) { @@ -1530,7 +1530,12 @@ export const singingStore = createPartialStore({ if (!previewSynth) { throw new Error("previewSynth is undefined."); } - previewSynth.noteOn("immediately", noteNumber, duration); + previewSynth.noteOn( + "immediately", + noteNumber, + duration, + state.savingSetting.audioOutputDevice, + ); }, }, diff --git a/src/type/globals.d.ts b/src/type/globals.d.ts index 6c761fa17f..98eca3982b 100644 --- a/src/type/globals.d.ts +++ b/src/type/globals.d.ts @@ -9,6 +9,11 @@ declare global { setSinkId(deviceID: string): Promise; // setSinkIdを認識してくれないため } + interface AudioContext { + sinkId: string; + setSinkId: (sinkId: string) => Promise; + } + interface Window { readonly [SandboxKey]: import("./preload").Sandbox; } From 31aac0fcb8c82a68fb040efdbc9e27c2dbbcaf2c Mon Sep 17 00:00:00 2001 From: X-20A <155217226+X-20A@users.noreply.github.com> Date: Sat, 23 Nov 2024 13:22:38 +0900 Subject: [PATCH 08/11] =?UTF-8?q?=E8=A8=AD=E5=AE=9A=E3=82=BF=E3=82=A4?= =?UTF-8?q?=E3=83=9F=E3=83=B3=E3=82=B0=E3=81=AE=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/App.vue | 4 ++ .../Dialog/SettingDialog/SettingDialog.vue | 3 + src/sing/audioRendering.ts | 62 +++++++++---------- src/store/singing.ts | 19 +++--- src/type/globals.d.ts | 1 - 5 files changed, 49 insertions(+), 40 deletions(-) diff --git a/src/components/App.vue b/src/components/App.vue index e1cc5c9a42..318abd37f4 100644 --- a/src/components/App.vue +++ b/src/components/App.vue @@ -31,6 +31,7 @@ import SingEditor from "@/components/Sing/SingEditor.vue"; import { EngineId } from "@/type/preload"; import ErrorBoundary from "@/components/ErrorBoundary.vue"; import { useStore } from "@/store"; +import { applyDeviceId } from "@/store/singing"; import { useHotkeyManager } from "@/plugins/hotkeyPlugin"; import AllDialog from "@/components/Dialog/AllDialog.vue"; import MenuBar from "@/components/Menu/MenuBar/MenuBar.vue"; @@ -163,5 +164,8 @@ onMounted(async () => { } else { isProjectFileLoaded.value = false; } + + // デバイスIDをTransportとPolySynthに設定 + void applyDeviceId(store.state.savingSetting.audioOutputDevice); }); diff --git a/src/components/Dialog/SettingDialog/SettingDialog.vue b/src/components/Dialog/SettingDialog/SettingDialog.vue index d8cc3f35c7..ea9efa3d09 100644 --- a/src/components/Dialog/SettingDialog/SettingDialog.vue +++ b/src/components/Dialog/SettingDialog/SettingDialog.vue @@ -477,6 +477,7 @@ import BaseSelectItem from "@/components/Base/BaseSelectItem.vue"; import BaseCheckbox from "@/components/Base/BaseCheckbox.vue"; import BaseTooltip from "@/components/Base/BaseTooltip.vue"; import { useStore } from "@/store"; +import { applyDeviceId } from "@/store/singing"; import { DEFAULT_AUDIO_FILE_NAME_TEMPLATE, DEFAULT_SONG_AUDIO_FILE_NAME_TEMPLATE, @@ -628,6 +629,8 @@ const currentAudioOutputDeviceComputed = computed({ set: (device) => { if (device) { handleSavingSettingChange("audioOutputDevice", device); + // デバイスIDをTransportとPolySynthに設定 + void applyDeviceId(store.state.savingSetting.audioOutputDevice); } }, }); diff --git a/src/sing/audioRendering.ts b/src/sing/audioRendering.ts index 1414dfd8ba..c729ec3bcd 100644 --- a/src/sing/audioRendering.ts +++ b/src/sing/audioRendering.ts @@ -94,6 +94,21 @@ export class Transport { this._time = value; } } + set sinkId(device: string) { + device = device ? device : ""; + if (this.audioContext.setSinkId) { + this.audioContext + .setSinkId(device === "default" ? "" : device) + .catch((err: unknown) => { + void showAlertDialog({ + type: "error", + title: "エラー", + message: "再生デバイスが見つかりません", + }); + throw err; + }); + } + } /** * @param audioContext 音声コンテキスト @@ -197,26 +212,11 @@ export class Transport { /** * 再生を開始します。すでに再生中の場合は何も行いません。 - * @param device 再生させるデバイスのID、指定が無ければデフォルトで再生 */ - start(device?: string) { + start() { if (this._state === "started") return; const contextTime = this.audioContext.currentTime; - device = device ? device : ""; - if (this.audioContext.setSinkId) { - this.audioContext - .setSinkId(device === "default" ? "" : device) - .catch((err: unknown) => { - void showAlertDialog({ - type: "error", - title: "エラー", - message: "再生デバイスが見つかりません", - }); - throw err; - }); - } - this._state = "started"; this.startContextTime = contextTime; @@ -793,6 +793,21 @@ export class PolySynth implements Instrument { get output(): AudioNode { return this.gainNode; } + set sinkId(device: string) { + device = device ? device : ""; + if (this.audioContext.setSinkId) { + this.audioContext + .setSinkId(device === "default" ? "" : device) + .catch((err: unknown) => { + void showAlertDialog({ + type: "error", + title: "エラー", + message: "再生デバイスが見つかりません", + }); + throw err; + }); + } + } constructor(audioContext: AudioContext, options?: PolySynthOptions) { this.audioContext = audioContext; @@ -822,27 +837,12 @@ export class PolySynth implements Instrument { * @param contextTime ノートオンを行う時刻(コンテキスト時刻) * @param noteNumber MIDIノート番号 * @param duration ノートの長さ(秒) - * @param device 再生させるデバイスのID、指定が無ければデフォルトで再生 */ noteOn( contextTime: number | "immediately", noteNumber: number, duration?: number, - device?: string, ) { - device = device ? device : ""; - if (this.audioContext.setSinkId) { - this.audioContext - .setSinkId(device === "default" ? "" : device) - .catch((err: unknown) => { - void showAlertDialog({ - type: "error", - title: "エラー", - message: "再生デバイスが見つかりません", - }); - throw err; - }); - } let voice = this.voices.find((value) => { return value.isActive && value.noteNumber === noteNumber; }); diff --git a/src/store/singing.ts b/src/store/singing.ts index 999bee4569..872eb3723a 100644 --- a/src/store/singing.ts +++ b/src/store/singing.ts @@ -551,6 +551,7 @@ let clipper: Clipper | undefined; // NOTE: テスト時はAudioContextが存在しない if (window.AudioContext) { audioContext = new AudioContext(); + // 読込時にここでAudioContextにsetSinkIdできると簡単かつ確実だけど... transport = new Transport(audioContext); previewSynth = new PolySynth(audioContext); mainChannelStrip = new ChannelStrip(audioContext); @@ -753,6 +754,13 @@ const getSelectedTrackWithFallback = (partialState: { return getOrThrow(partialState.tracks, partialState._selectedTrackId); }; +export const applyDeviceId = (device: string) => { + if (transport && previewSynth) { + transport.sinkId = device; + previewSynth.sinkId = device; + } +}; + export const singingStoreState: SingingStoreState = { tpqn: DEFAULT_TPQN, tempos: [createDefaultTempo(0)], @@ -1482,7 +1490,7 @@ export const singingStore = createPartialStore({ } mutations.SET_PLAYBACK_STATE({ nowPlaying: true }); - transport.start(state.savingSetting.audioOutputDevice); + transport.start(); animationTimer.start(() => { playheadPosition.value = getters.SECOND_TO_TICK(transport.time); }); @@ -1521,7 +1529,7 @@ export const singingStore = createPartialStore({ PLAY_PREVIEW_SOUND: { async action( - { state }, + _, { noteNumber, duration }: { noteNumber: number; duration?: number }, ) { if (!audioContext) { @@ -1530,12 +1538,7 @@ export const singingStore = createPartialStore({ if (!previewSynth) { throw new Error("previewSynth is undefined."); } - previewSynth.noteOn( - "immediately", - noteNumber, - duration, - state.savingSetting.audioOutputDevice, - ); + previewSynth.noteOn("immediately", noteNumber, duration); }, }, diff --git a/src/type/globals.d.ts b/src/type/globals.d.ts index 98eca3982b..9708bb15e5 100644 --- a/src/type/globals.d.ts +++ b/src/type/globals.d.ts @@ -10,7 +10,6 @@ declare global { } interface AudioContext { - sinkId: string; setSinkId: (sinkId: string) => Promise; } From 8c5dfae40488df89f7a459b5608a0160d688bf5f Mon Sep 17 00:00:00 2001 From: X-20A <155217226+X-20A@users.noreply.github.com> Date: Sat, 23 Nov 2024 14:49:02 +0900 Subject: [PATCH 09/11] =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Dialog/SettingDialog/SettingDialog.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Dialog/SettingDialog/SettingDialog.vue b/src/components/Dialog/SettingDialog/SettingDialog.vue index ea9efa3d09..c167eda867 100644 --- a/src/components/Dialog/SettingDialog/SettingDialog.vue +++ b/src/components/Dialog/SettingDialog/SettingDialog.vue @@ -630,7 +630,7 @@ const currentAudioOutputDeviceComputed = computed({ if (device) { handleSavingSettingChange("audioOutputDevice", device); // デバイスIDをTransportとPolySynthに設定 - void applyDeviceId(store.state.savingSetting.audioOutputDevice); + void applyDeviceId(device); } }, }); From a5391e6f4f76f899b0e53013746b3d03a0649cb5 Mon Sep 17 00:00:00 2001 From: X-20A <155217226+X-20A@users.noreply.github.com> Date: Sat, 23 Nov 2024 22:30:47 +0900 Subject: [PATCH 10/11] =?UTF-8?q?=E8=A8=AD=E5=AE=9A=E5=A0=B4=E6=89=80?= =?UTF-8?q?=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/App.vue | 4 ---- src/components/Dialog/SettingDialog/SettingDialog.vue | 3 --- src/store/singing.ts | 11 +++++++---- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/components/App.vue b/src/components/App.vue index 318abd37f4..e1cc5c9a42 100644 --- a/src/components/App.vue +++ b/src/components/App.vue @@ -31,7 +31,6 @@ import SingEditor from "@/components/Sing/SingEditor.vue"; import { EngineId } from "@/type/preload"; import ErrorBoundary from "@/components/ErrorBoundary.vue"; import { useStore } from "@/store"; -import { applyDeviceId } from "@/store/singing"; import { useHotkeyManager } from "@/plugins/hotkeyPlugin"; import AllDialog from "@/components/Dialog/AllDialog.vue"; import MenuBar from "@/components/Menu/MenuBar/MenuBar.vue"; @@ -164,8 +163,5 @@ onMounted(async () => { } else { isProjectFileLoaded.value = false; } - - // デバイスIDをTransportとPolySynthに設定 - void applyDeviceId(store.state.savingSetting.audioOutputDevice); }); diff --git a/src/components/Dialog/SettingDialog/SettingDialog.vue b/src/components/Dialog/SettingDialog/SettingDialog.vue index c167eda867..d8cc3f35c7 100644 --- a/src/components/Dialog/SettingDialog/SettingDialog.vue +++ b/src/components/Dialog/SettingDialog/SettingDialog.vue @@ -477,7 +477,6 @@ import BaseSelectItem from "@/components/Base/BaseSelectItem.vue"; import BaseCheckbox from "@/components/Base/BaseCheckbox.vue"; import BaseTooltip from "@/components/Base/BaseTooltip.vue"; import { useStore } from "@/store"; -import { applyDeviceId } from "@/store/singing"; import { DEFAULT_AUDIO_FILE_NAME_TEMPLATE, DEFAULT_SONG_AUDIO_FILE_NAME_TEMPLATE, @@ -629,8 +628,6 @@ const currentAudioOutputDeviceComputed = computed({ set: (device) => { if (device) { handleSavingSettingChange("audioOutputDevice", device); - // デバイスIDをTransportとPolySynthに設定 - void applyDeviceId(device); } }, }); diff --git a/src/store/singing.ts b/src/store/singing.ts index 872eb3723a..acc119f4ab 100644 --- a/src/store/singing.ts +++ b/src/store/singing.ts @@ -551,7 +551,6 @@ let clipper: Clipper | undefined; // NOTE: テスト時はAudioContextが存在しない if (window.AudioContext) { audioContext = new AudioContext(); - // 読込時にここでAudioContextにsetSinkIdできると簡単かつ確実だけど... transport = new Transport(audioContext); previewSynth = new PolySynth(audioContext); mainChannelStrip = new ChannelStrip(audioContext); @@ -754,7 +753,7 @@ const getSelectedTrackWithFallback = (partialState: { return getOrThrow(partialState.tracks, partialState._selectedTrackId); }; -export const applyDeviceId = (device: string) => { +const applyDeviceId = (device: string) => { if (transport && previewSynth) { transport.sinkId = device; previewSynth.sinkId = device; @@ -1465,10 +1464,11 @@ export const singingStore = createPartialStore({ }, SET_PLAYHEAD_POSITION: { - async action({ getters }, { position }: { position: number }) { + async action({ state, getters }, { position }: { position: number }) { if (!transport) { throw new Error("transport is undefined."); } + applyDeviceId(state.savingSetting.audioOutputDevice); playheadPosition.value = position; transport.time = getters.TICK_TO_SECOND(position); }, @@ -1490,6 +1490,8 @@ export const singingStore = createPartialStore({ } mutations.SET_PLAYBACK_STATE({ nowPlaying: true }); + applyDeviceId(state.savingSetting.audioOutputDevice); + transport.start(); animationTimer.start(() => { playheadPosition.value = getters.SECOND_TO_TICK(transport.time); @@ -1529,7 +1531,7 @@ export const singingStore = createPartialStore({ PLAY_PREVIEW_SOUND: { async action( - _, + { state }, { noteNumber, duration }: { noteNumber: number; duration?: number }, ) { if (!audioContext) { @@ -1538,6 +1540,7 @@ export const singingStore = createPartialStore({ if (!previewSynth) { throw new Error("previewSynth is undefined."); } + applyDeviceId(state.savingSetting.audioOutputDevice); previewSynth.noteOn("immediately", noteNumber, duration); }, }, From ded8c1088e9fffe5227cef210a18ffcec270ce04 Mon Sep 17 00:00:00 2001 From: X-20A <155217226+X-20A@users.noreply.github.com> Date: Sun, 24 Nov 2024 19:44:25 +0900 Subject: [PATCH 11/11] =?UTF-8?q?=E3=82=BF=E3=82=A4=E3=83=9F=E3=83=B3?= =?UTF-8?q?=E3=82=B0=E5=86=8D=E8=AA=BF=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/App.vue | 8 ++++++++ src/sing/audioRendering.ts | 35 ++--------------------------------- src/store/singing.ts | 25 +++++++++++++++---------- 3 files changed, 25 insertions(+), 43 deletions(-) diff --git a/src/components/App.vue b/src/components/App.vue index e1cc5c9a42..6718bdf19b 100644 --- a/src/components/App.vue +++ b/src/components/App.vue @@ -31,6 +31,7 @@ import SingEditor from "@/components/Sing/SingEditor.vue"; import { EngineId } from "@/type/preload"; import ErrorBoundary from "@/components/ErrorBoundary.vue"; import { useStore } from "@/store"; +import { applyDeviceId } from "@/store/singing"; import { useHotkeyManager } from "@/plugins/hotkeyPlugin"; import AllDialog from "@/components/Dialog/AllDialog.vue"; import MenuBar from "@/components/Menu/MenuBar/MenuBar.vue"; @@ -97,6 +98,13 @@ watchEffect(() => { setThemeToCss(theme); }); +// 再生デバイスの初期化と変更の監視 +watchEffect(() => { + applyDeviceId(store.state.savingSetting.audioOutputDevice).catch((e) => { + console.error(e); + }); +}); + // ソフトウェアを初期化 const { hotkeyManager } = useHotkeyManager(); const isEnginesReady = ref(false); diff --git a/src/sing/audioRendering.ts b/src/sing/audioRendering.ts index c729ec3bcd..c96ef4d531 100644 --- a/src/sing/audioRendering.ts +++ b/src/sing/audioRendering.ts @@ -4,7 +4,6 @@ import { linearToDecibel, } from "@/sing/domain"; import { Timer } from "@/sing/utility"; -import { showAlertDialog } from "@/components/Dialog/Dialog"; const getEarliestSchedulableContextTime = (audioContext: BaseAudioContext) => { const renderQuantumSize = 128; @@ -94,21 +93,6 @@ export class Transport { this._time = value; } } - set sinkId(device: string) { - device = device ? device : ""; - if (this.audioContext.setSinkId) { - this.audioContext - .setSinkId(device === "default" ? "" : device) - .catch((err: unknown) => { - void showAlertDialog({ - type: "error", - title: "エラー", - message: "再生デバイスが見つかりません", - }); - throw err; - }); - } - } /** * @param audioContext 音声コンテキスト @@ -782,7 +766,7 @@ export type PolySynthOptions = { * ポリフォニックシンセサイザーです。 */ export class PolySynth implements Instrument { - private readonly audioContext: AudioContext; + private readonly audioContext: BaseAudioContext; private readonly gainNode: GainNode; private readonly oscParams: SynthOscParams; private readonly filterParams: SynthFilterParams; @@ -793,23 +777,8 @@ export class PolySynth implements Instrument { get output(): AudioNode { return this.gainNode; } - set sinkId(device: string) { - device = device ? device : ""; - if (this.audioContext.setSinkId) { - this.audioContext - .setSinkId(device === "default" ? "" : device) - .catch((err: unknown) => { - void showAlertDialog({ - type: "error", - title: "エラー", - message: "再生デバイスが見つかりません", - }); - throw err; - }); - } - } - constructor(audioContext: AudioContext, options?: PolySynthOptions) { + constructor(audioContext: BaseAudioContext, options?: PolySynthOptions) { this.audioContext = audioContext; this.oscParams = options?.osc ?? { type: "square", diff --git a/src/store/singing.ts b/src/store/singing.ts index f0289f8881..8e0f9a96c6 100644 --- a/src/store/singing.ts +++ b/src/store/singing.ts @@ -106,6 +106,7 @@ import { uuid4 } from "@/helpers/random"; import { convertToWavFileData } from "@/sing/convertToWavFileData"; import { generateWriteErrorMessage } from "@/helpers/fileHelper"; import path from "@/helpers/path"; +import { showAlertDialog } from "@/components/Dialog/Dialog"; const logger = createLogger("store/singing"); @@ -753,10 +754,18 @@ const getSelectedTrackWithFallback = (partialState: { return getOrThrow(partialState.tracks, partialState._selectedTrackId); }; -const applyDeviceId = (device: string) => { - if (transport && previewSynth) { - transport.sinkId = device; - previewSynth.sinkId = device; +// AudioContextに再生デバイスのIDを設定 +export const applyDeviceId = async (device: string) => { + if (audioContext) { + const sinkId = device === "default" ? "" : device; + audioContext.setSinkId(sinkId).catch((err: unknown) => { + void showAlertDialog({ + type: "error", + title: "エラー", + message: "再生デバイスが見つかりません", + }); + throw err; + }); } }; @@ -1464,11 +1473,10 @@ export const singingStore = createPartialStore({ }, SET_PLAYHEAD_POSITION: { - async action({ state, getters }, { position }: { position: number }) { + async action({ getters }, { position }: { position: number }) { if (!transport) { throw new Error("transport is undefined."); } - applyDeviceId(state.savingSetting.audioOutputDevice); playheadPosition.value = position; transport.time = getters.TICK_TO_SECOND(position); }, @@ -1490,8 +1498,6 @@ export const singingStore = createPartialStore({ } mutations.SET_PLAYBACK_STATE({ nowPlaying: true }); - applyDeviceId(state.savingSetting.audioOutputDevice); - transport.start(); animationTimer.start(() => { playheadPosition.value = getters.SECOND_TO_TICK(transport.time); @@ -1531,7 +1537,7 @@ export const singingStore = createPartialStore({ PLAY_PREVIEW_SOUND: { async action( - { state }, + _, { noteNumber, duration }: { noteNumber: number; duration?: number }, ) { if (!audioContext) { @@ -1540,7 +1546,6 @@ export const singingStore = createPartialStore({ if (!previewSynth) { throw new Error("previewSynth is undefined."); } - applyDeviceId(state.savingSetting.audioOutputDevice); previewSynth.noteOn("immediately", noteNumber, duration); }, },