MagicPot / js /audio.js
zhanglei
fix voice
ab5909a
Raw
History Blame Contribute Delete
15.6 kB
// 音频管理系统
class AudioManager {
constructor() {
this.audioContext = null;
this.sounds = new Map();
this.isEnabled = true;
this.volume = 0.7;
this.isInitialized = false;
this.hasShownInteractionHint = false;
// 音效配置
this.soundConfig = {
potActivate: { frequency: 440, duration: 0.5, type: 'magic' },
cooking: { frequency: 220, duration: 0.3, type: 'bubble' },
foodPop: { frequency: 660, duration: 0.2, type: 'pop' },
animalAppear: { frequency: 880, duration: 0.4, type: 'chirp' },
girlAppear: { frequency: 1320, duration: 0.6, type: 'sparkle' },
celebration: { frequency: 1760, duration: 1.0, type: 'fanfare' },
stopCooking: { frequency: 330, duration: 0.4, type: 'whoosh' }
};
this.init();
}
async init() {
try {
// 创建音频上下文(暂停状态)
this.audioContext = new (window.AudioContext || window.webkitAudioContext)();
// 注册用户交互事件来启动音频上下文
this.setupUserInteractionHandlers();
console.log('音频系统已准备,等待用户交互启动');
} catch (error) {
console.warn('音频系统初始化失败:', error);
this.isEnabled = false;
}
}
setupUserInteractionHandlers() {
const startAudio = async () => {
if (this.audioContext && this.audioContext.state === 'suspended') {
try {
await this.audioContext.resume();
// 音频上下文启动后,预生成音效
if (!this.isInitialized) {
this.preGenerateSounds();
this.isInitialized = true;
console.log('音频系统初始化完成');
}
// 隐藏音频激活提示
this.hideAudioActivationHint();
} catch (error) {
console.warn('启动音频上下文失败:', error);
}
}
};
// 监听各种用户交互事件
const events = ['click', 'touchstart', 'keydown'];
const handler = () => {
startAudio();
// 移除事件监听器,只需要启动一次
events.forEach(event => {
document.removeEventListener(event, handler);
});
};
events.forEach(event => {
document.addEventListener(event, handler, { once: true });
});
}
showAudioActivationHint() {
const hint = document.getElementById('audioActivationHint');
if (hint) {
hint.style.display = 'block';
console.log('显示音频激活提示');
}
}
hideAudioActivationHint() {
const hint = document.getElementById('audioActivationHint');
if (hint) {
hint.style.display = 'none';
console.log('隐藏音频激活提示');
}
}
async ensureAudioContext() {
if (!this.audioContext) return false;
if (this.audioContext.state === 'suspended') {
try {
await this.audioContext.resume();
// 如果音频上下文刚刚启动,且还未初始化,则初始化音效
if (!this.isInitialized) {
this.preGenerateSounds();
this.isInitialized = true;
console.log('音频系统延迟初始化完成');
}
// 隐藏音频激活提示
this.hideAudioActivationHint();
} catch (error) {
console.warn('无法恢复音频上下文:', error);
return false;
}
}
return this.audioContext.state === 'running';
}
preGenerateSounds() {
// 预生成所有音效的音频数据
Object.keys(this.soundConfig).forEach(soundName => {
this.generateSound(soundName);
});
}
generateSound(soundName) {
const config = this.soundConfig[soundName];
if (!config) return null;
const sampleRate = this.audioContext.sampleRate;
const duration = config.duration;
const length = sampleRate * duration;
const buffer = this.audioContext.createBuffer(1, length, sampleRate);
const data = buffer.getChannelData(0);
// 根据音效类型生成不同的波形
switch (config.type) {
case 'magic':
this.generateMagicSound(data, config, sampleRate);
break;
case 'bubble':
this.generateBubbleSound(data, config, sampleRate);
break;
case 'pop':
this.generatePopSound(data, config, sampleRate);
break;
case 'chirp':
this.generateChirpSound(data, config, sampleRate);
break;
case 'sparkle':
this.generateSparkleSound(data, config, sampleRate);
break;
case 'fanfare':
this.generateFanfareSound(data, config, sampleRate);
break;
case 'whoosh':
this.generateWhooshSound(data, config, sampleRate);
break;
default:
this.generateSimpleBeep(data, config, sampleRate);
}
this.sounds.set(soundName, buffer);
return buffer;
}
generateMagicSound(data, config, sampleRate) {
// 魔法激活音效 - 上升的音调带有颤音
const baseFreq = config.frequency;
const length = data.length;
for (let i = 0; i < length; i++) {
const t = i / sampleRate;
const progress = i / length;
// 上升的频率
const freq = baseFreq * (1 + progress * 0.5);
// 颤音效果
const vibrato = 1 + 0.1 * Math.sin(2 * Math.PI * 6 * t);
// 主音调
const wave = Math.sin(2 * Math.PI * freq * vibrato * t);
// 包络(淡入淡出)
const envelope = Math.sin(Math.PI * progress) * Math.exp(-progress * 2);
data[i] = wave * envelope * 0.3;
}
}
generateBubbleSound(data, config, sampleRate) {
// 烹饪冒泡音效
const baseFreq = config.frequency;
const length = data.length;
for (let i = 0; i < length; i++) {
const t = i / sampleRate;
const progress = i / length;
// 随机频率变化模拟冒泡
const randomFactor = 1 + 0.3 * (Math.random() - 0.5);
const freq = baseFreq * randomFactor;
// 多个正弦波叠加
const wave1 = Math.sin(2 * Math.PI * freq * t);
const wave2 = Math.sin(2 * Math.PI * freq * 1.5 * t) * 0.5;
const wave3 = Math.sin(2 * Math.PI * freq * 2 * t) * 0.25;
const wave = wave1 + wave2 + wave3;
// 快速衰减
const envelope = Math.exp(-progress * 8);
data[i] = wave * envelope * 0.2;
}
}
generatePopSound(data, config, sampleRate) {
// 食物弹出音效 - 短促的爆破声
const baseFreq = config.frequency;
const length = data.length;
for (let i = 0; i < length; i++) {
const t = i / sampleRate;
const progress = i / length;
// 快速下降的频率
const freq = baseFreq * (1 - progress * 0.8);
// 噪音成分
const noise = (Math.random() - 0.5) * 0.3;
// 主音调
const wave = Math.sin(2 * Math.PI * freq * t) + noise;
// 非常快的衰减
const envelope = Math.exp(-progress * 15);
data[i] = wave * envelope * 0.4;
}
}
generateChirpSound(data, config, sampleRate) {
// 动物出现音效 - 鸟叫声
const baseFreq = config.frequency;
const length = data.length;
for (let i = 0; i < length; i++) {
const t = i / sampleRate;
const progress = i / length;
// 频率变化模拟鸟叫
const freqMod = Math.sin(2 * Math.PI * 8 * t) * 0.2;
const freq = baseFreq * (1 + freqMod);
const wave = Math.sin(2 * Math.PI * freq * t);
// 自然的包络
const envelope = Math.sin(Math.PI * progress) * Math.exp(-progress * 3);
data[i] = wave * envelope * 0.25;
}
}
generateSparkleSound(data, config, sampleRate) {
// 小女孩出现音效 - 闪亮的铃声
const baseFreq = config.frequency;
const length = data.length;
for (let i = 0; i < length; i++) {
const t = i / sampleRate;
const progress = i / length;
// 多个谐波创造铃声效果
const wave1 = Math.sin(2 * Math.PI * baseFreq * t);
const wave2 = Math.sin(2 * Math.PI * baseFreq * 2 * t) * 0.5;
const wave3 = Math.sin(2 * Math.PI * baseFreq * 3 * t) * 0.25;
const wave4 = Math.sin(2 * Math.PI * baseFreq * 4 * t) * 0.125;
const wave = wave1 + wave2 + wave3 + wave4;
// 缓慢衰减
const envelope = Math.exp(-progress * 1.5);
data[i] = wave * envelope * 0.2;
}
}
generateFanfareSound(data, config, sampleRate) {
// 庆祝音效 - 号角声
const baseFreq = config.frequency;
const length = data.length;
for (let i = 0; i < length; i++) {
const t = i / sampleRate;
const progress = i / length;
// 三和弦
const freq1 = baseFreq;
const freq2 = baseFreq * 1.25; // 大三度
const freq3 = baseFreq * 1.5; // 完全五度
const wave1 = Math.sin(2 * Math.PI * freq1 * t);
const wave2 = Math.sin(2 * Math.PI * freq2 * t) * 0.8;
const wave3 = Math.sin(2 * Math.PI * freq3 * t) * 0.6;
const wave = wave1 + wave2 + wave3;
// 持续的包络
const envelope = Math.sin(Math.PI * progress) * 0.8;
data[i] = wave * envelope * 0.15;
}
}
generateWhooshSound(data, config, sampleRate) {
// 停止音效 - 风声
const length = data.length;
for (let i = 0; i < length; i++) {
const progress = i / length;
// 白噪音
const noise = (Math.random() - 0.5) * 2;
// 低通滤波效果(简化)
const cutoff = 1000 * (1 - progress);
const filtered = noise * Math.exp(-progress * 3);
// 快速衰减
const envelope = Math.exp(-progress * 5);
data[i] = filtered * envelope * 0.3;
}
}
generateSimpleBeep(data, config, sampleRate) {
// 简单的蜂鸣声
const freq = config.frequency;
const length = data.length;
for (let i = 0; i < length; i++) {
const t = i / sampleRate;
const progress = i / length;
const wave = Math.sin(2 * Math.PI * freq * t);
const envelope = Math.exp(-progress * 3);
data[i] = wave * envelope * 0.3;
}
}
async playSound(soundName, volume = 1.0) {
if (!this.isEnabled) return;
const canPlay = await this.ensureAudioContext();
if (!canPlay) {
// 如果是第一次尝试播放,显示视觉提示
if (!this.hasShownInteractionHint) {
this.showAudioActivationHint();
this.hasShownInteractionHint = true;
}
return;
}
if (!this.isInitialized) {
console.warn('音频系统尚未初始化');
return;
}
const buffer = this.sounds.get(soundName);
if (!buffer) {
console.warn(`音效 ${soundName} 不存在`);
return;
}
try {
const source = this.audioContext.createBufferSource();
const gainNode = this.audioContext.createGain();
source.buffer = buffer;
source.connect(gainNode);
gainNode.connect(this.audioContext.destination);
// 设置音量
gainNode.gain.value = this.volume * volume;
source.start();
// 清理资源
source.onended = () => {
source.disconnect();
gainNode.disconnect();
};
} catch (error) {
console.warn(`播放音效 ${soundName} 失败:`, error);
}
}
// 播放特定音效的便捷方法
playPotActivate() {
this.playSound('potActivate');
}
playCooking() {
this.playSound('cooking', 0.5);
}
playFoodPop() {
this.playSound('foodPop', 0.8);
}
playAnimalAppear() {
this.playSound('animalAppear', 0.6);
}
playGirlAppear() {
this.playSound('girlAppear', 0.7);
}
playCelebration() {
this.playSound('celebration', 0.9);
}
playStopCooking() {
this.playSound('stopCooking', 0.6);
}
// 播放随机音调的食物弹出声
playRandomFoodPop() {
const randomPitch = 0.8 + Math.random() * 0.4; // 0.8 到 1.2
this.playSound('foodPop', randomPitch);
}
// 设置音量
setVolume(volume) {
this.volume = Math.max(0, Math.min(1, volume));
}
// 启用/禁用音效
setEnabled(enabled) {
this.isEnabled = enabled;
localStorage.setItem('magicPotAudioEnabled', enabled.toString());
}
// 获取音效状态
getStatus() {
return {
enabled: this.isEnabled,
initialized: this.isInitialized,
volume: this.volume,
soundCount: this.sounds.size,
contextState: this.audioContext ? this.audioContext.state : 'none'
};
}
// 测试所有音效
async testAllSounds() {
const soundNames = Object.keys(this.soundConfig);
for (let i = 0; i < soundNames.length; i++) {
const soundName = soundNames[i];
console.log(`测试音效: ${soundName}`);
await this.playSound(soundName);
// 等待一段时间再播放下一个
await new Promise(resolve => setTimeout(resolve, 800));
}
}
}
// 创建全局音频管理器实例
window.audioManager = new AudioManager();