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;
"bubbleGame": string;
"sfx": string;
"soundWillBePlayed": string;
"_announcement": {
"forExistingUsers": string;
"forExistingUsersDescription": string;

View File

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

View File

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

View File

@ -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);

View File

@ -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,

View File

@ -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));

View File

@ -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');
}
}

View File

@ -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);
}

View File

@ -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);
}

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>
</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>
@ -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<number | null>(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);
}
});

View File

@ -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,

View File

@ -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() {

View File

@ -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)

View File

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

View File

@ -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;