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 }}
+
+ {{ i18n.ts.mute }}
+
+
+
@@ -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;