/** * 对话音效角色类型 */ export type DialogCharacterType = | 'undertale' // 类似Undertale的方波音效 | 'zelda' // 三角波为基础的音效 | 'pokemon' // 高音调短促音效 | 'robot' // 机械音效 | 'pixie' // 精灵/仙女音效 | 'monster' // 怪物/低沉音效 | 'custom'; // 自定义音效 /** * 音效配置接口 */ export interface DialogSoundConfig { oscillatorType: OscillatorType; // 振荡器类型 baseFrequency: number; // 基础频率 frequencyVariation: number; // 频率变化范围 gain: number; // 音量 attackTime: number; // 起音时间 releaseTime: number; // 释音时间 duration: number; // 持续时间 highPassFrequency?: number; // 高通滤波器频率 lowPassFrequency?: number; // 低通滤波器频率 distortion?: number; // 失真度 detune?: number; // 音高微调 } /** * 为每种角色类型预设的音效配置 */ export const CHARACTER_SOUND_CONFIGS: Record, DialogSoundConfig> = { // Undertale风格 - 清脆的方波 undertale: { oscillatorType: 'square', baseFrequency: 380, frequencyVariation: 10, gain: 0.05, attackTime: 0.01, releaseTime: 0.02, duration: 0.05, highPassFrequency: 700 }, // 塞尔达风格 - 三角波 zelda: { oscillatorType: 'triangle', baseFrequency: 300, frequencyVariation: 8, gain: 0.08, attackTime: 0.01, releaseTime: 0.08, duration: 0.12, highPassFrequency: 400 }, // 宝可梦风格 - 高音调短促 pokemon: { oscillatorType: 'sine', baseFrequency: 850, frequencyVariation: 5, gain: 0.06, attackTime: 0.005, releaseTime: 0.015, duration: 0.03 }, // 机器人风格 - 方波带失真 robot: { oscillatorType: 'square', baseFrequency: 200, frequencyVariation: 12, gain: 0.04, attackTime: 0.005, releaseTime: 0.03, duration: 0.07, distortion: 15, detune: 5 }, // 精灵/仙女风格 - 高音调正弦波 pixie: { oscillatorType: 'sine', baseFrequency: 1200, frequencyVariation: 15, gain: 0.03, attackTime: 0.005, releaseTime: 0.1, duration: 0.15, highPassFrequency: 900 }, // 怪物/低沉风格 - 低音锯齿波 monster: { oscillatorType: 'sawtooth', baseFrequency: 150, frequencyVariation: 6, gain: 0.07, attackTime: 0.02, releaseTime: 0.05, duration: 0.1, lowPassFrequency: 300, distortion: 8 } }; /** * 用于存储AudioContext单例 */ let audioContextInstance: AudioContext | null = null; /** * 获取或创建AudioContext */ export function getAudioContext(): AudioContext { if (!audioContextInstance) { audioContextInstance = new (window.AudioContext || (window as any).webkitAudioContext)(); } return audioContextInstance; } /** * 销毁AudioContext */ export function destroyAudioContext(): void { if (audioContextInstance && audioContextInstance.state !== 'closed') { audioContextInstance.close(); audioContextInstance = null; } } /** * 创建失真效果 */ function createDistortionEffect( audioContext: AudioContext, distortionAmount: number ): WaveShaperNode { const waveShaperNode = audioContext.createWaveShaper(); // 创建失真曲线 function makeDistortionCurve(amount: number): Float32Array { const k = amount; const samples = 44100; const curve = new Float32Array(samples); for (let i = 0; i < samples; ++i) { const x = (i * 2) / samples - 1; curve[i] = ((3 + k) * x * 0.5 * (1 - Math.abs(x))) / (3 + k * Math.abs(x)); } return curve; } waveShaperNode.curve = makeDistortionCurve(distortionAmount); return waveShaperNode; } /** * 播放单个字符的对话音效 * @param char 要为其播放音效的字符 * @param characterType 角色类型 * @param customConfig 可选的自定义配置 * @returns 音效的持续时间(秒) */ export function playDialogSound( char: string, characterType: DialogCharacterType | number, customConfig?: Partial ): number { // 空格不播放音效 if (char === ' ') { return 0; } // 获取音频上下文 const audioContext = getAudioContext(); const now = audioContext.currentTime; // 获取基础配置 let config: DialogSoundConfig; if (characterType === 'custom' && customConfig) { // 使用自定义配置 config = { oscillatorType: customConfig.oscillatorType || 'square', baseFrequency: customConfig.baseFrequency || 400, frequencyVariation: customConfig.frequencyVariation || 10, gain: customConfig.gain || 0.05, attackTime: customConfig.attackTime || 0.01, releaseTime: customConfig.releaseTime || 0.05, duration: customConfig.duration || 0.08, ...customConfig }; } else { // 使用预设配置 if (typeof characterType === 'number') { config = Object.values(CHARACTER_SOUND_CONFIGS)[characterType] || CHARACTER_SOUND_CONFIGS.undertale; } else { config = { ...CHARACTER_SOUND_CONFIGS[characterType as Exclude] }; } // 应用自定义配置覆盖(如果有) if (customConfig) { config = { ...config, ...customConfig }; } } // 创建音频节点 const oscillator = audioContext.createOscillator(); const gainNode = audioContext.createGain(); // 设置振荡器 oscillator.type = config.oscillatorType; // 基于字符计算频率变化 const charCode = char.charCodeAt(0); const frequencyOffset = (charCode % config.frequencyVariation) / config.frequencyVariation; // 设置频率和可选的失谐 oscillator.frequency.value = config.baseFrequency + (config.baseFrequency * 0.2 * frequencyOffset); if (config.detune) { // 添加轻微的失谐,使声音更有特色 oscillator.detune.value = (charCode % 10) * config.detune; } // 连接音频节点链 let currentNode: AudioNode = oscillator; // 连接到增益节点 currentNode.connect(gainNode); currentNode = gainNode; // 添加失真效果(如果指定) if (config.distortion && config.distortion > 0) { const distortionNode = createDistortionEffect(audioContext, config.distortion); currentNode.connect(distortionNode); currentNode = distortionNode; } // 添加低通滤波器(如果指定) if (config.lowPassFrequency) { const lowPassFilter = audioContext.createBiquadFilter(); lowPassFilter.type = 'lowpass'; lowPassFilter.frequency.value = config.lowPassFrequency; lowPassFilter.Q.value = 1; currentNode.connect(lowPassFilter); currentNode = lowPassFilter; } // 添加高通滤波器(如果指定) if (config.highPassFrequency) { const highPassFilter = audioContext.createBiquadFilter(); highPassFilter.type = 'highpass'; highPassFilter.frequency.value = config.highPassFrequency; highPassFilter.Q.value = 1; currentNode.connect(highPassFilter); currentNode = highPassFilter; } // 连接到输出 currentNode.connect(audioContext.destination); // 设置音量包络 gainNode.gain.setValueAtTime(0, now); gainNode.gain.linearRampToValueAtTime(config.gain, now + config.attackTime); gainNode.gain.linearRampToValueAtTime(0, now + config.duration); // 播放音效 oscillator.start(now); oscillator.stop(now + config.duration); return config.duration; }