Add synthesized bark sound and vibration feedback
This commit is contained in:
parent
57aeb989d4
commit
b43a9ea35a
98
js/barkSound.js
Normal file
98
js/barkSound.js
Normal 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);
|
||||
});
|
||||
}
|
||||
@ -2,6 +2,7 @@ import { SCREEN_WIDTH, SCREEN_HEIGHT } from './render';
|
||||
import AudioRecorder from './audioRecorder';
|
||||
import AIOpponent from './ai';
|
||||
import storage from './storage';
|
||||
import playBark, { playWinSound } from './barkSound';
|
||||
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
@ -279,8 +280,9 @@ export default class Main {
|
||||
this.combo = status.combo;
|
||||
if (status.isBark && this.state === 'BATTLE') {
|
||||
this.energy = Math.min(100, this.energy + status.pushPower * 100);
|
||||
playBark();
|
||||
try {
|
||||
if (wx.vibrateShort) wx.vibrateShort({ type: 'light' });
|
||||
if (wx.vibrateShort) wx.vibrateShort();
|
||||
} catch (e) {}
|
||||
}
|
||||
},
|
||||
@ -434,7 +436,10 @@ export default class Main {
|
||||
|
||||
this.rewardWP = isWin ? (this.combo >= 3 ? 50 : 30) : 5;
|
||||
this.wp = storage.addWP(this.rewardWP);
|
||||
if (isWin) storage.addWin();
|
||||
if (isWin) {
|
||||
storage.addWin();
|
||||
playWinSound();
|
||||
}
|
||||
|
||||
this.drawResult();
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user