This commit is contained in:
syuilo 2024-01-09 12:57:47 +09:00
parent e1bea4e125
commit d125cc3aac
15 changed files with 67 additions and 35 deletions

1
locales/index.d.ts vendored
View File

@ -1194,6 +1194,7 @@ export interface Locale {
"enableQuickAddMfmFunction": string; "enableQuickAddMfmFunction": string;
"bubbleGame": string; "bubbleGame": string;
"sfx": string; "sfx": string;
"soundWillBePlayed": string;
"_announcement": { "_announcement": {
"forExistingUsers": string; "forExistingUsers": string;
"forExistingUsersDescription": string; "forExistingUsersDescription": string;

View File

@ -1191,6 +1191,7 @@ addMfmFunction: "装飾を追加"
enableQuickAddMfmFunction: "高度なMFMのピッカーを表示する" enableQuickAddMfmFunction: "高度なMFMのピッカーを表示する"
bubbleGame: "バブルゲーム" bubbleGame: "バブルゲーム"
sfx: "効果音" sfx: "効果音"
soundWillBePlayed: "サウンドが再生されます"
_announcement: _announcement:
forExistingUsers: "既存ユーザーのみ" forExistingUsers: "既存ユーザーのみ"

View File

@ -271,7 +271,7 @@ export async function mainBoot() {
main.on('unreadAntenna', () => { main.on('unreadAntenna', () => {
updateAccount({ hasUnreadAntenna: true }); updateAccount({ hasUnreadAntenna: true });
sound.play('antenna'); sound.playMisskeySfx('antenna');
}); });
main.on('readAllAnnouncements', () => { main.on('readAllAnnouncements', () => {

View File

@ -345,7 +345,7 @@ function react(viaKeyboard = false): void {
pleaseLogin(); pleaseLogin();
showMovedDialog(); showMovedDialog();
if (appearNote.value.reactionAcceptance === 'likeOnly') { if (appearNote.value.reactionAcceptance === 'likeOnly') {
sound.play('reaction'); sound.playMisskeySfx('reaction');
if (props.mock) { if (props.mock) {
return; return;
@ -365,7 +365,7 @@ function react(viaKeyboard = false): void {
} else { } else {
blur(); blur();
reactionPicker.show(reactButton.value, reaction => { reactionPicker.show(reactButton.value, reaction => {
sound.play('reaction'); sound.playMisskeySfx('reaction');
if (props.mock) { if (props.mock) {
emit('reaction', reaction); emit('reaction', reaction);

View File

@ -370,7 +370,7 @@ function react(viaKeyboard = false): void {
pleaseLogin(); pleaseLogin();
showMovedDialog(); showMovedDialog();
if (appearNote.value.reactionAcceptance === 'likeOnly') { if (appearNote.value.reactionAcceptance === 'likeOnly') {
sound.play('reaction'); sound.playMisskeySfx('reaction');
misskeyApi('notes/reactions/create', { misskeyApi('notes/reactions/create', {
noteId: appearNote.value.id, noteId: appearNote.value.id,
@ -386,7 +386,7 @@ function react(viaKeyboard = false): void {
} else { } else {
blur(); blur();
reactionPicker.show(reactButton.value, reaction => { reactionPicker.show(reactButton.value, reaction => {
sound.play('reaction'); sound.playMisskeySfx('reaction');
misskeyApi('notes/reactions/create', { misskeyApi('notes/reactions/create', {
noteId: appearNote.value.id, noteId: appearNote.value.id,

View File

@ -62,7 +62,7 @@ async function toggleReaction() {
if (confirm.canceled) return; if (confirm.canceled) return;
if (oldReaction !== props.reaction) { if (oldReaction !== props.reaction) {
sound.play('reaction'); sound.playMisskeySfx('reaction');
} }
if (mock) { if (mock) {
@ -81,7 +81,7 @@ async function toggleReaction() {
} }
}); });
} else { } else {
sound.play('reaction'); sound.playMisskeySfx('reaction');
if (mock) { if (mock) {
emit('reactionToggled', props.reaction, (props.count + 1)); emit('reactionToggled', props.reaction, (props.count + 1));

View File

@ -81,7 +81,7 @@ function prepend(note) {
emit('note'); emit('note');
if (props.sound) { if (props.sound) {
sound.play($i && (note.userId === $i.id) ? 'noteMy' : 'note'); sound.playMisskeySfx($i && (note.userId === $i.id) ? 'noteMy' : 'note');
} }
} }

View File

@ -91,7 +91,7 @@ function onClick(ev: MouseEvent) {
icon: 'ti ti-plus', icon: 'ti ti-plus',
action: () => { action: () => {
react(`:${props.name}:`); react(`:${props.name}:`);
sound.play('reaction'); sound.playMisskeySfx('reaction');
}, },
}] : [])], ev.currentTarget ?? ev.target); }] : [])], ev.currentTarget ?? ev.target);
} }

View File

@ -55,7 +55,7 @@ function onClick(ev: MouseEvent) {
icon: 'ti ti-plus', icon: 'ti ti-plus',
action: () => { action: () => {
react(props.emoji); react(props.emoji);
sound.play('reaction'); sound.playMisskeySfx('reaction');
}, },
}] : [])], ev.currentTarget ?? ev.target); }] : [])], ev.currentTarget ?? ev.target);
} }

View File

@ -24,6 +24,14 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkButton primary gradate large rounded inline @click="start">{{ i18n.ts.start }}</MkButton> <MkButton primary gradate large rounded inline @click="start">{{ i18n.ts.start }}</MkButton>
</div> </div>
</div> </div>
<div :class="$style.frameInner">
<div class="_gaps" style="padding: 16px;">
<div style="font-size: 90%;"><i class="ti ti-music"></i> {{ i18n.ts.soundWillBePlayed }}</div>
<MkSwitch v-model="mute">
<template #label>{{ i18n.ts.mute }}</template>
</MkSwitch>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -159,6 +167,7 @@ import { $i } from '@/account.js';
import { DropAndFusionGame, Mono } from '@/scripts/drop-and-fusion-engine.js'; import { DropAndFusionGame, Mono } from '@/scripts/drop-and-fusion-engine.js';
import * as sound from '@/scripts/sound.js'; import * as sound from '@/scripts/sound.js';
import MkRange from '@/components/MkRange.vue'; import MkRange from '@/components/MkRange.vue';
import MkSwitch from '@/components/MkSwitch.vue';
const NORMAL_BASE_SIZE = 30; const NORMAL_BASE_SIZE = 30;
const NORAML_MONOS: Mono[] = [{ const NORAML_MONOS: Mono[] = [{
@ -409,6 +418,7 @@ const gameOver = ref(false);
const gameStarted = ref(false); const gameStarted = ref(false);
const highScore = ref<number | null>(null); const highScore = ref<number | null>(null);
const showConfig = ref(false); const showConfig = ref(false);
const mute = ref(false);
const bgmVolume = ref(defaultStore.state.dropAndFusion.bgmVolume); const bgmVolume = ref(defaultStore.state.dropAndFusion.bgmVolume);
const sfxVolume = ref(defaultStore.state.dropAndFusion.sfxVolume); const sfxVolume = ref(defaultStore.state.dropAndFusion.sfxVolume);
@ -545,7 +555,7 @@ async function start() {
width: GAME_WIDTH, width: GAME_WIDTH,
height: GAME_HEIGHT, height: GAME_HEIGHT,
canvas: canvasEl.value!, canvas: canvasEl.value!,
sfxVolume: sfxVolume.value, sfxVolume: mute.value ? 0 : sfxVolume.value,
...( ...(
gameMode.value === 'normal' ? { gameMode.value === 'normal' ? {
monoDefinitions: NORAML_MONOS, monoDefinitions: NORAML_MONOS,
@ -565,7 +575,9 @@ async function start() {
} }
const bgmBuffer = await sound.loadAudio('/client-assets/drop-and-fusion/bgm_1.mp3'); const bgmBuffer = await sound.loadAudio('/client-assets/drop-and-fusion/bgm_1.mp3');
if (!bgmBuffer) return; if (!bgmBuffer) return;
bgmNodes = sound.createSourceNode(bgmBuffer, bgmVolume.value); bgmNodes = sound.createSourceNode(bgmBuffer, {
volume: mute.value ? 0 : bgmVolume.value,
});
if (!bgmNodes) return; if (!bgmNodes) return;
bgmNodes.soundSource.loop = true; bgmNodes.soundSource.loop = true;
bgmNodes.soundSource.start(); bgmNodes.soundSource.start();
@ -574,14 +586,14 @@ async function start() {
watch(bgmVolume, (newValue, oldValue) => { watch(bgmVolume, (newValue, oldValue) => {
if (bgmNodes) { if (bgmNodes) {
bgmNodes.gainNode.gain.value = newValue; bgmNodes.gainNode.gain.value = mute.value ? 0 : newValue;
} }
}); });
watch(sfxVolume, (newValue, oldValue) => { watch(sfxVolume, (newValue, oldValue) => {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (game) { if (game) {
game.setSfxVolume(newValue); game.setSfxVolume(mute.value ? 0 : newValue);
} }
}); });

View File

@ -33,7 +33,7 @@ import MkRange from '@/components/MkRange.vue';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import * as os from '@/os.js'; import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.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'; import { selectFile } from '@/scripts/select-file.js';
const props = defineProps<{ const props = defineProps<{
@ -119,7 +119,7 @@ function listen() {
return; return;
} }
playFile(type.value === '_driveFile_' ? { playMisskeySfxFile(type.value === '_driveFile_' ? {
type: '_driveFile_', type: '_driveFile_',
fileId: fileId.value as string, fileId: fileId.value as string,
fileUrl: fileUrl.value as string, fileUrl: fileUrl.value as string,

View File

@ -233,7 +233,11 @@ export class DropAndFusionGame extends EventEmitter<{
// TODO: 効果音再生はコンポーネント側の責務なので移動する // TODO: 効果音再生はコンポーネント側の責務なので移動する
const pan = ((newX / this.gameWidth) - 0.5) * 2; 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('monoAdded', nextMono);
this.emit('fusioned', newX, newY, additionalScore); 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 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 pan = ((((bodyA.position.x + bodyB.position.x) / 2) / this.gameWidth) - 0.5) * 2;
const pitch = soundPitchMin + ((soundPitchMax - soundPitchMin) * (1 - (Math.min(10, energy) / 10))); 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: 効果音再生はコンポーネント側の責務なので移動する // TODO: 効果音再生はコンポーネント側の責務なので移動する
const pan = ((x / this.gameWidth) - 0.5) * 2; 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() { public hold() {

View File

@ -126,13 +126,13 @@ export async function loadAudio(url: string, options?: { useCache?: boolean; })
* *
* @param type * @param type
*/ */
export function play(operationType: OperationType) { export function playMisskeySfx(operationType: OperationType) {
const sound = defaultStore.state[`sound_${operationType}`]; const sound = defaultStore.state[`sound_${operationType}`];
if (_DEV_) console.log('play', operationType, sound); if (_DEV_) console.log('play', operationType, sound);
if (sound.type == null || !canPlay) return; if (sound.type == null || !canPlay) return;
canPlay = false; canPlay = false;
playFile(sound).finally(() => { playMisskeySfxFile(sound).finally(() => {
// ごく短時間に音が重複しないように // ごく短時間に音が重複しないように
setTimeout(() => { setTimeout(() => {
canPlay = true; canPlay = true;
@ -144,7 +144,7 @@ export function play(operationType: OperationType) {
* *
* @param soundStore * @param soundStore
*/ */
export async function playFile(soundStore: SoundStore) { export async function playMisskeySfxFile(soundStore: SoundStore) {
if (soundStore.type === null || (soundStore.type === '_driveFile_' && !soundStore.fileUrl)) { if (soundStore.type === null || (soundStore.type === '_driveFile_' && !soundStore.fileUrl)) {
return; 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 url = soundStore.type === '_driveFile_' ? soundStore.fileUrl : `/client-assets/sounds/${soundStore.type}.mp3`;
const buffer = await loadAudio(url); const buffer = await loadAudio(url);
if (!buffer) return; 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) { export async function playUrl(url: string, opts: {
const masterVolume = defaultStore.state.sound_masterVolume; volume?: number;
if (isMute() || masterVolume === 0 || volume === 0) { pan?: number;
playbackRate?: number;
}) {
if (opts.volume === 0) {
return; return;
} }
const buffer = await loadAudio(url); const buffer = await loadAudio(url);
if (!buffer) return; 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; soundSource: AudioBufferSourceNode;
panNode: StereoPannerNode; panNode: StereoPannerNode;
gainNode: GainNode; gainNode: GainNode;
} { } {
const masterVolume = defaultStore.state.sound_masterVolume;
const panNode = ctx.createStereoPanner(); const panNode = ctx.createStereoPanner();
panNode.pan.value = pan; panNode.pan.value = opts.pan ?? 0;
const gainNode = ctx.createGain(); const gainNode = ctx.createGain();
gainNode.gain.value = masterVolume * volume;
gainNode.gain.value = opts.volume ?? 1;
const soundSource = ctx.createBufferSource(); const soundSource = ctx.createBufferSource();
soundSource.buffer = buffer; soundSource.buffer = buffer;
soundSource.playbackRate.value = playbackRate; soundSource.playbackRate.value = opts.playbackRate ?? 1;
soundSource soundSource
.connect(panNode) .connect(panNode)
.connect(gainNode) .connect(gainNode)

View File

@ -83,7 +83,7 @@ function onNotification(notification: Misskey.entities.Notification, isClient =
}, 6000); }, 6000);
} }
sound.play('notification'); sound.playMisskeySfx('notification');
} }
if ($i) { if ($i) {

View File

@ -123,7 +123,7 @@ const onStats = (stats) => {
current[domain].delayed = stats[domain].delayed; current[domain].delayed = stats[domain].delayed;
if (current[domain].waiting > 0 && widgetProps.sound && jammedAudioBuffer.value && !jammedSoundNodePlaying.value) { 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) { if (soundNode) {
jammedSoundNodePlaying.value = true; jammedSoundNodePlaying.value = true;
soundNode.onended = () => jammedSoundNodePlaying.value = false; soundNode.onended = () => jammedSoundNodePlaying.value = false;