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] 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; }