From d125cc3aacced1a2aa9ab561611d54f8c63fbed8 Mon Sep 17 00:00:00 2001 From: syuilo Date: Tue, 9 Jan 2024 12:57:47 +0900 Subject: [PATCH] tweak --- locales/index.d.ts | 1 + locales/ja-JP.yml | 1 + packages/frontend/src/boot/main-boot.ts | 2 +- packages/frontend/src/components/MkNote.vue | 4 +-- .../src/components/MkNoteDetailed.vue | 4 +-- .../components/MkReactionsViewer.reaction.vue | 4 +-- .../frontend/src/components/MkTimeline.vue | 2 +- .../src/components/global/MkCustomEmoji.vue | 2 +- .../src/components/global/MkEmoji.vue | 2 +- .../frontend/src/pages/drop-and-fusion.vue | 20 ++++++++--- .../src/pages/settings/sounds.sound.vue | 4 +-- .../src/scripts/drop-and-fusion-engine.ts | 17 +++++++-- packages/frontend/src/scripts/sound.ts | 35 +++++++++++-------- packages/frontend/src/ui/_common_/common.vue | 2 +- .../frontend/src/widgets/WidgetJobQueue.vue | 2 +- 15 files changed, 67 insertions(+), 35 deletions(-) diff --git a/locales/index.d.ts b/locales/index.d.ts index 8b6beba046..96bc9099dd 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -1194,6 +1194,7 @@ export interface Locale { "enableQuickAddMfmFunction": string; "bubbleGame": string; "sfx": string; + "soundWillBePlayed": string; "_announcement": { "forExistingUsers": string; "forExistingUsersDescription": string; diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 7ccbce3ad8..c28fde56cb 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1191,6 +1191,7 @@ addMfmFunction: "装飾を追加" enableQuickAddMfmFunction: "高度なMFMのピッカーを表示する" bubbleGame: "バブルゲーム" sfx: "効果音" +soundWillBePlayed: "サウンドが再生されます" _announcement: forExistingUsers: "既存ユーザーのみ" diff --git a/packages/frontend/src/boot/main-boot.ts b/packages/frontend/src/boot/main-boot.ts index 5011ce9e74..bdb145b39a 100644 --- a/packages/frontend/src/boot/main-boot.ts +++ b/packages/frontend/src/boot/main-boot.ts @@ -271,7 +271,7 @@ export async function mainBoot() { main.on('unreadAntenna', () => { updateAccount({ hasUnreadAntenna: true }); - sound.play('antenna'); + sound.playMisskeySfx('antenna'); }); main.on('readAllAnnouncements', () => { diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue index 3ec9c3c46a..9c4354ef5f 100644 --- a/packages/frontend/src/components/MkNote.vue +++ b/packages/frontend/src/components/MkNote.vue @@ -345,7 +345,7 @@ function react(viaKeyboard = false): void { pleaseLogin(); showMovedDialog(); if (appearNote.value.reactionAcceptance === 'likeOnly') { - sound.play('reaction'); + sound.playMisskeySfx('reaction'); if (props.mock) { return; @@ -365,7 +365,7 @@ function react(viaKeyboard = false): void { } else { blur(); reactionPicker.show(reactButton.value, reaction => { - sound.play('reaction'); + sound.playMisskeySfx('reaction'); if (props.mock) { emit('reaction', reaction); diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue index 6f0c0323cc..e941827d74 100644 --- a/packages/frontend/src/components/MkNoteDetailed.vue +++ b/packages/frontend/src/components/MkNoteDetailed.vue @@ -370,7 +370,7 @@ function react(viaKeyboard = false): void { pleaseLogin(); showMovedDialog(); if (appearNote.value.reactionAcceptance === 'likeOnly') { - sound.play('reaction'); + sound.playMisskeySfx('reaction'); misskeyApi('notes/reactions/create', { noteId: appearNote.value.id, @@ -386,7 +386,7 @@ function react(viaKeyboard = false): void { } else { blur(); reactionPicker.show(reactButton.value, reaction => { - sound.play('reaction'); + sound.playMisskeySfx('reaction'); misskeyApi('notes/reactions/create', { noteId: appearNote.value.id, diff --git a/packages/frontend/src/components/MkReactionsViewer.reaction.vue b/packages/frontend/src/components/MkReactionsViewer.reaction.vue index 2e75f444da..5ca09fa822 100644 --- a/packages/frontend/src/components/MkReactionsViewer.reaction.vue +++ b/packages/frontend/src/components/MkReactionsViewer.reaction.vue @@ -62,7 +62,7 @@ async function toggleReaction() { if (confirm.canceled) return; if (oldReaction !== props.reaction) { - sound.play('reaction'); + sound.playMisskeySfx('reaction'); } if (mock) { @@ -81,7 +81,7 @@ async function toggleReaction() { } }); } else { - sound.play('reaction'); + sound.playMisskeySfx('reaction'); if (mock) { emit('reactionToggled', props.reaction, (props.count + 1)); diff --git a/packages/frontend/src/components/MkTimeline.vue b/packages/frontend/src/components/MkTimeline.vue index 63f779dbde..8a5076ea1d 100644 --- a/packages/frontend/src/components/MkTimeline.vue +++ b/packages/frontend/src/components/MkTimeline.vue @@ -81,7 +81,7 @@ function prepend(note) { emit('note'); if (props.sound) { - sound.play($i && (note.userId === $i.id) ? 'noteMy' : 'note'); + sound.playMisskeySfx($i && (note.userId === $i.id) ? 'noteMy' : 'note'); } } diff --git a/packages/frontend/src/components/global/MkCustomEmoji.vue b/packages/frontend/src/components/global/MkCustomEmoji.vue index a9643d68ca..dd3fe77251 100644 --- a/packages/frontend/src/components/global/MkCustomEmoji.vue +++ b/packages/frontend/src/components/global/MkCustomEmoji.vue @@ -91,7 +91,7 @@ function onClick(ev: MouseEvent) { icon: 'ti ti-plus', action: () => { react(`:${props.name}:`); - sound.play('reaction'); + sound.playMisskeySfx('reaction'); }, }] : [])], ev.currentTarget ?? ev.target); } diff --git a/packages/frontend/src/components/global/MkEmoji.vue b/packages/frontend/src/components/global/MkEmoji.vue index f6b21343b6..cbdb3881c6 100644 --- a/packages/frontend/src/components/global/MkEmoji.vue +++ b/packages/frontend/src/components/global/MkEmoji.vue @@ -55,7 +55,7 @@ function onClick(ev: MouseEvent) { icon: 'ti ti-plus', action: () => { react(props.emoji); - sound.play('reaction'); + sound.playMisskeySfx('reaction'); }, }] : [])], ev.currentTarget ?? ev.target); } diff --git a/packages/frontend/src/pages/drop-and-fusion.vue b/packages/frontend/src/pages/drop-and-fusion.vue index 64e374bc83..15ac07489a 100644 --- a/packages/frontend/src/pages/drop-and-fusion.vue +++ b/packages/frontend/src/pages/drop-and-fusion.vue @@ -24,6 +24,14 @@ SPDX-License-Identifier: AGPL-3.0-only {{ i18n.ts.start }} +
+
+
{{ i18n.ts.soundWillBePlayed }}
+ + + +
+
@@ -159,6 +167,7 @@ import { $i } from '@/account.js'; import { DropAndFusionGame, Mono } from '@/scripts/drop-and-fusion-engine.js'; import * as sound from '@/scripts/sound.js'; import MkRange from '@/components/MkRange.vue'; +import MkSwitch from '@/components/MkSwitch.vue'; const NORMAL_BASE_SIZE = 30; const NORAML_MONOS: Mono[] = [{ @@ -409,6 +418,7 @@ const gameOver = ref(false); const gameStarted = ref(false); const highScore = ref(null); const showConfig = ref(false); +const mute = ref(false); const bgmVolume = ref(defaultStore.state.dropAndFusion.bgmVolume); const sfxVolume = ref(defaultStore.state.dropAndFusion.sfxVolume); @@ -545,7 +555,7 @@ async function start() { width: GAME_WIDTH, height: GAME_HEIGHT, canvas: canvasEl.value!, - sfxVolume: sfxVolume.value, + sfxVolume: mute.value ? 0 : sfxVolume.value, ...( gameMode.value === 'normal' ? { monoDefinitions: NORAML_MONOS, @@ -565,7 +575,9 @@ async function start() { } const bgmBuffer = await sound.loadAudio('/client-assets/drop-and-fusion/bgm_1.mp3'); if (!bgmBuffer) return; - bgmNodes = sound.createSourceNode(bgmBuffer, bgmVolume.value); + bgmNodes = sound.createSourceNode(bgmBuffer, { + volume: mute.value ? 0 : bgmVolume.value, + }); if (!bgmNodes) return; bgmNodes.soundSource.loop = true; bgmNodes.soundSource.start(); @@ -574,14 +586,14 @@ async function start() { watch(bgmVolume, (newValue, oldValue) => { if (bgmNodes) { - bgmNodes.gainNode.gain.value = newValue; + bgmNodes.gainNode.gain.value = mute.value ? 0 : newValue; } }); watch(sfxVolume, (newValue, oldValue) => { // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (game) { - game.setSfxVolume(newValue); + game.setSfxVolume(mute.value ? 0 : newValue); } }); diff --git a/packages/frontend/src/pages/settings/sounds.sound.vue b/packages/frontend/src/pages/settings/sounds.sound.vue index 57bafce0ac..798980b3d1 100644 --- a/packages/frontend/src/pages/settings/sounds.sound.vue +++ b/packages/frontend/src/pages/settings/sounds.sound.vue @@ -33,7 +33,7 @@ import MkRange from '@/components/MkRange.vue'; import { i18n } from '@/i18n.js'; import * as os from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; -import { playFile, soundsTypes, getSoundDuration } from '@/scripts/sound.js'; +import { playMisskeySfxFile, soundsTypes, getSoundDuration } from '@/scripts/sound.js'; import { selectFile } from '@/scripts/select-file.js'; const props = defineProps<{ @@ -119,7 +119,7 @@ function listen() { return; } - playFile(type.value === '_driveFile_' ? { + playMisskeySfxFile(type.value === '_driveFile_' ? { type: '_driveFile_', fileId: fileId.value as string, fileUrl: fileUrl.value as string, diff --git a/packages/frontend/src/scripts/drop-and-fusion-engine.ts b/packages/frontend/src/scripts/drop-and-fusion-engine.ts index 633777c27e..5d08718b22 100644 --- a/packages/frontend/src/scripts/drop-and-fusion-engine.ts +++ b/packages/frontend/src/scripts/drop-and-fusion-engine.ts @@ -233,7 +233,11 @@ export class DropAndFusionGame extends EventEmitter<{ // TODO: 効果音再生はコンポーネント側の責務なので移動する const pan = ((newX / this.gameWidth) - 0.5) * 2; - sound.playUrl('/client-assets/drop-and-fusion/bubble2.mp3', this.sfxVolume, pan, nextMono.sfxPitch); + sound.playUrl('/client-assets/drop-and-fusion/bubble2.mp3', { + volume: this.sfxVolume, + pan, + playbackRate: nextMono.sfxPitch, + }); this.emit('monoAdded', nextMono); this.emit('fusioned', newX, newY, additionalScore); @@ -335,7 +339,11 @@ export class DropAndFusionGame extends EventEmitter<{ const vol = ((Math.min(maxCollisionEnergyForSound, energy - minCollisionEnergyForSound) / maxCollisionEnergyForSound) / 4) * this.sfxVolume; const pan = ((((bodyA.position.x + bodyB.position.x) / 2) / this.gameWidth) - 0.5) * 2; const pitch = soundPitchMin + ((soundPitchMax - soundPitchMin) * (1 - (Math.min(10, energy) / 10))); - sound.playUrl('/client-assets/drop-and-fusion/poi1.mp3', vol, pan, pitch); + sound.playUrl('/client-assets/drop-and-fusion/poi1.mp3', { + volume: vol, + pan, + playbackRate: pitch, + }); } } } @@ -400,7 +408,10 @@ export class DropAndFusionGame extends EventEmitter<{ // TODO: 効果音再生はコンポーネント側の責務なので移動する const pan = ((x / this.gameWidth) - 0.5) * 2; - sound.playUrl('/client-assets/drop-and-fusion/poi2.mp3', this.sfxVolume, pan); + sound.playUrl('/client-assets/drop-and-fusion/poi2.mp3', { + volume: this.sfxVolume, + pan, + }); } public hold() { diff --git a/packages/frontend/src/scripts/sound.ts b/packages/frontend/src/scripts/sound.ts index 8c289ade0f..142ddf87c9 100644 --- a/packages/frontend/src/scripts/sound.ts +++ b/packages/frontend/src/scripts/sound.ts @@ -126,13 +126,13 @@ export async function loadAudio(url: string, options?: { useCache?: boolean; }) * 既定のスプライトを再生する * @param type スプライトの種類を指定 */ -export function play(operationType: OperationType) { +export function playMisskeySfx(operationType: OperationType) { const sound = defaultStore.state[`sound_${operationType}`]; if (_DEV_) console.log('play', operationType, sound); if (sound.type == null || !canPlay) return; canPlay = false; - playFile(sound).finally(() => { + playMisskeySfxFile(sound).finally(() => { // ごく短時間に音が重複しないように setTimeout(() => { canPlay = true; @@ -144,7 +144,7 @@ export function play(operationType: OperationType) { * サウンド設定形式で指定された音声を再生する * @param soundStore サウンド設定 */ -export async function playFile(soundStore: SoundStore) { +export async function playMisskeySfxFile(soundStore: SoundStore) { if (soundStore.type === null || (soundStore.type === '_driveFile_' && !soundStore.fileUrl)) { return; } @@ -155,35 +155,42 @@ export async function playFile(soundStore: SoundStore) { const url = soundStore.type === '_driveFile_' ? soundStore.fileUrl : `/client-assets/sounds/${soundStore.type}.mp3`; const buffer = await loadAudio(url); if (!buffer) return; - createSourceNode(buffer, soundStore.volume).soundSource.start(); + const volume = soundStore.volume * masterVolume; + createSourceNode(buffer, { volume }).soundSource.start(); } -export async function playUrl(url: string, volume = 1, pan = 0, playbackRate = 1) { - const masterVolume = defaultStore.state.sound_masterVolume; - if (isMute() || masterVolume === 0 || volume === 0) { +export async function playUrl(url: string, opts: { + volume?: number; + pan?: number; + playbackRate?: number; +}) { + if (opts.volume === 0) { return; } const buffer = await loadAudio(url); if (!buffer) return; - createSourceNode(buffer, volume, pan, playbackRate).soundSource.start(); + createSourceNode(buffer, opts).soundSource.start(); } -export function createSourceNode(buffer: AudioBuffer, volume: number, pan = 0, playbackRate = 1): { +export function createSourceNode(buffer: AudioBuffer, opts: { + volume?: number; + pan?: number; + playbackRate?: number; +}): { soundSource: AudioBufferSourceNode; panNode: StereoPannerNode; gainNode: GainNode; } { - const masterVolume = defaultStore.state.sound_masterVolume; - const panNode = ctx.createStereoPanner(); - panNode.pan.value = pan; + panNode.pan.value = opts.pan ?? 0; const gainNode = ctx.createGain(); - gainNode.gain.value = masterVolume * volume; + + gainNode.gain.value = opts.volume ?? 1; const soundSource = ctx.createBufferSource(); soundSource.buffer = buffer; - soundSource.playbackRate.value = playbackRate; + soundSource.playbackRate.value = opts.playbackRate ?? 1; soundSource .connect(panNode) .connect(gainNode) diff --git a/packages/frontend/src/ui/_common_/common.vue b/packages/frontend/src/ui/_common_/common.vue index 78af49cdc2..0ec036c5cb 100644 --- a/packages/frontend/src/ui/_common_/common.vue +++ b/packages/frontend/src/ui/_common_/common.vue @@ -83,7 +83,7 @@ function onNotification(notification: Misskey.entities.Notification, isClient = }, 6000); } - sound.play('notification'); + sound.playMisskeySfx('notification'); } if ($i) { diff --git a/packages/frontend/src/widgets/WidgetJobQueue.vue b/packages/frontend/src/widgets/WidgetJobQueue.vue index 89ad3bf323..877406fe95 100644 --- a/packages/frontend/src/widgets/WidgetJobQueue.vue +++ b/packages/frontend/src/widgets/WidgetJobQueue.vue @@ -123,7 +123,7 @@ const onStats = (stats) => { current[domain].delayed = stats[domain].delayed; if (current[domain].waiting > 0 && widgetProps.sound && jammedAudioBuffer.value && !jammedSoundNodePlaying.value) { - const soundNode = sound.createSourceNode(jammedAudioBuffer.value, 1)?.soundSource; + const soundNode = sound.createSourceNode(jammedAudioBuffer.value, {}).soundSource; if (soundNode) { jammedSoundNodePlaying.value = true; soundNode.onended = () => jammedSoundNodePlaying.value = false;