wofgame-front-end/src/components/dialog-sound-effects.ts
2025-06-27 08:42:27 +08:00

277 lines
8.0 KiB
TypeScript

/**
* 对话音效角色类型
*/
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<Exclude<DialogCharacterType, 'custom'>, 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<DialogSoundConfig>
): 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<DialogCharacterType, 'custom'>] };
}
// 应用自定义配置覆盖(如果有)
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;
}