Add synthesized bark sound and vibration feedback

This commit is contained in:
Developer 2026-06-10 23:22:30 +08:00
parent 57aeb989d4
commit b43a9ea35a
2 changed files with 105 additions and 2 deletions

98
js/barkSound.js Normal file
View File

@ -0,0 +1,98 @@
/**
* 简单的合成狗叫声
* 使用 Web Audio API无需外部音频文件
*/
let audioCtx = null;
function getAudioContext() {
if (!audioCtx) {
const AC = window.AudioContext || window.webkitAudioContext;
if (AC) {
audioCtx = new AC();
}
}
return audioCtx;
}
export default function playBark() {
const ctx = getAudioContext();
if (!ctx) return;
// 如果 AudioContext 被暂停(某些浏览器/微信环境),先 resume
if (ctx.state === 'suspended') {
ctx.resume();
}
const now = ctx.currentTime;
const duration = 0.18; // 吠叫持续约 180ms
// 1. 噪声源(吠叫的嘶哑感)
const sampleRate = ctx.sampleRate;
const bufferSize = sampleRate * duration;
const noiseBuffer = ctx.createBuffer(1, bufferSize, sampleRate);
const output = noiseBuffer.getChannelData(0);
for (let i = 0; i < bufferSize; i++) {
output[i] = Math.random() * 2 - 1;
}
const noise = ctx.createBufferSource();
noise.buffer = noiseBuffer;
// 2. 带通滤波器,集中在 600-1200Hz 类似狗叫的频段
const bandpass = ctx.createBiquadFilter();
bandpass.type = 'bandpass';
bandpass.frequency.value = 900;
bandpass.Q.value = 0.8;
// 3. 低频振荡器(吠叫的胸腔共鸣感)
const osc = ctx.createOscillator();
osc.type = 'sawtooth';
osc.frequency.setValueAtTime(180, now);
osc.frequency.exponentialRampToValueAtTime(120, now + duration);
const oscGain = ctx.createGain();
oscGain.gain.value = 0.25;
// 4. 主音量包络快速Attack快速Decay
const masterGain = ctx.createGain();
masterGain.gain.setValueAtTime(0, now);
masterGain.gain.linearRampToValueAtTime(0.6, now + 0.02);
masterGain.gain.exponentialRampToValueAtTime(0.01, now + duration);
// 连接
noise.connect(bandpass);
bandpass.connect(masterGain);
osc.connect(oscGain);
oscGain.connect(masterGain);
masterGain.connect(ctx.destination);
// 播放
noise.start(now);
noise.stop(now + duration);
osc.start(now);
osc.stop(now + duration);
}
export function playWinSound() {
const ctx = getAudioContext();
if (!ctx) return;
if (ctx.state === 'suspended') ctx.resume();
const now = ctx.currentTime;
const notes = [523.25, 659.25, 783.99, 1046.50]; // C E G C'
notes.forEach((freq, i) => {
const osc = ctx.createOscillator();
osc.type = 'sine';
osc.frequency.value = freq;
const gain = ctx.createGain();
gain.gain.setValueAtTime(0, now + i * 0.1);
gain.gain.linearRampToValueAtTime(0.2, now + i * 0.1 + 0.02);
gain.gain.exponentialRampToValueAtTime(0.01, now + i * 0.1 + 0.25);
osc.connect(gain);
gain.connect(ctx.destination);
osc.start(now + i * 0.1);
osc.stop(now + i * 0.1 + 0.3);
});
}

View File

@ -2,6 +2,7 @@ import { SCREEN_WIDTH, SCREEN_HEIGHT } from './render';
import AudioRecorder from './audioRecorder'; import AudioRecorder from './audioRecorder';
import AIOpponent from './ai'; import AIOpponent from './ai';
import storage from './storage'; import storage from './storage';
import playBark, { playWinSound } from './barkSound';
const ctx = canvas.getContext('2d'); const ctx = canvas.getContext('2d');
@ -279,8 +280,9 @@ export default class Main {
this.combo = status.combo; this.combo = status.combo;
if (status.isBark && this.state === 'BATTLE') { if (status.isBark && this.state === 'BATTLE') {
this.energy = Math.min(100, this.energy + status.pushPower * 100); this.energy = Math.min(100, this.energy + status.pushPower * 100);
playBark();
try { try {
if (wx.vibrateShort) wx.vibrateShort({ type: 'light' }); if (wx.vibrateShort) wx.vibrateShort();
} catch (e) {} } catch (e) {}
} }
}, },
@ -434,7 +436,10 @@ export default class Main {
this.rewardWP = isWin ? (this.combo >= 3 ? 50 : 30) : 5; this.rewardWP = isWin ? (this.combo >= 3 ? 50 : 30) : 5;
this.wp = storage.addWP(this.rewardWP); this.wp = storage.addWP(this.rewardWP);
if (isWin) storage.addWin(); if (isWin) {
storage.addWin();
playWinSound();
}
this.drawResult(); this.drawResult();
} }