99 lines
2.7 KiB
JavaScript
99 lines
2.7 KiB
JavaScript
/**
|
||
* 简单的合成狗叫声
|
||
* 使用 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);
|
||
});
|
||
}
|