329 lines
8.6 KiB
Python
329 lines
8.6 KiB
Python
"""
|
||
音频工具类
|
||
提供音频处理相关的实用工具函数
|
||
"""
|
||
|
||
import math
|
||
from typing import Tuple, List, Dict, Any
|
||
from panda3d.core import Vec3
|
||
|
||
def calculate_distance(pos1: Tuple[float, float, float],
|
||
pos2: Tuple[float, float, float]) -> float:
|
||
"""
|
||
计算两点间距离
|
||
|
||
Args:
|
||
pos1: 第一个点 (x, y, z)
|
||
pos2: 第二个点 (x, y, z)
|
||
|
||
Returns:
|
||
距离
|
||
"""
|
||
dx = pos1[0] - pos2[0]
|
||
dy = pos1[1] - pos2[1]
|
||
dz = pos1[2] - pos2[2]
|
||
return math.sqrt(dx*dx + dy*dy + dz*dz)
|
||
|
||
|
||
def calculate_volume_attenuation(distance: float, max_distance: float = 50.0,
|
||
min_volume: float = 0.0, max_volume: float = 1.0) -> float:
|
||
"""
|
||
计算基于距离的音量衰减
|
||
|
||
Args:
|
||
distance: 距离
|
||
max_distance: 最大距离(在此距离外音量为最小值)
|
||
min_volume: 最小音量
|
||
max_volume: 最大音量
|
||
|
||
Returns:
|
||
计算后的音量
|
||
"""
|
||
if distance >= max_distance:
|
||
return min_volume
|
||
|
||
# 线性衰减
|
||
attenuation = 1.0 - (distance / max_distance)
|
||
volume = min_volume + (max_volume - min_volume) * attenuation
|
||
return max(min_volume, min(max_volume, volume))
|
||
|
||
|
||
def calculate_pan(position: Tuple[float, float, float],
|
||
listener_position: Tuple[float, float, float]) -> float:
|
||
"""
|
||
计算立体声平移(左/右声道平衡)
|
||
|
||
Args:
|
||
position: 音源位置
|
||
listener_position: 听者位置
|
||
|
||
Returns:
|
||
平移值 (-1.0 到 1.0,-1为左,1为右)
|
||
"""
|
||
# 计算相对位置
|
||
rel_x = position[0] - listener_position[0]
|
||
rel_z = position[2] - listener_position[2]
|
||
|
||
# 计算角度
|
||
angle = math.atan2(rel_x, rel_z)
|
||
|
||
# 转换为平移值
|
||
pan = math.sin(angle)
|
||
return max(-1.0, min(1.0, pan))
|
||
|
||
|
||
def interpolate_positions(start_pos: Tuple[float, float, float],
|
||
end_pos: Tuple[float, float, float],
|
||
steps: int) -> List[Tuple[float, float, float]]:
|
||
"""
|
||
在两点间插值生成位置列表
|
||
|
||
Args:
|
||
start_pos: 起始位置
|
||
end_pos: 结束位置
|
||
steps: 插值步数
|
||
|
||
Returns:
|
||
插值位置列表
|
||
"""
|
||
if steps <= 1:
|
||
return [start_pos, end_pos]
|
||
|
||
positions = []
|
||
for i in range(steps):
|
||
t = i / (steps - 1)
|
||
x = start_pos[0] + (end_pos[0] - start_pos[0]) * t
|
||
y = start_pos[1] + (end_pos[1] - start_pos[1]) * t
|
||
z = start_pos[2] + (end_pos[2] - start_pos[2]) * t
|
||
positions.append((x, y, z))
|
||
|
||
return positions
|
||
|
||
|
||
def validate_audio_file(filepath: str) -> Dict[str, Any]:
|
||
"""
|
||
验证音频文件
|
||
|
||
Args:
|
||
filepath: 音频文件路径
|
||
|
||
Returns:
|
||
验证结果字典
|
||
"""
|
||
import os
|
||
|
||
result = {
|
||
'valid': False,
|
||
'exists': False,
|
||
'file_size': 0,
|
||
'extension': '',
|
||
'supported': False
|
||
}
|
||
|
||
# 检查文件是否存在
|
||
if not os.path.exists(filepath):
|
||
result['error'] = '文件不存在'
|
||
return result
|
||
|
||
result['exists'] = True
|
||
|
||
# 获取文件信息
|
||
result['file_size'] = os.path.getsize(filepath)
|
||
_, extension = os.path.splitext(filepath)
|
||
result['extension'] = extension.lower()
|
||
|
||
# 检查是否为支持的格式
|
||
supported_formats = ['.wav', '.ogg', '.mp3']
|
||
result['supported'] = result['extension'] in supported_formats
|
||
|
||
# 验证通过
|
||
if result['supported'] and result['file_size'] > 0:
|
||
result['valid'] = True
|
||
|
||
return result
|
||
|
||
|
||
def generate_test_tone(frequency: float = 440.0, duration: float = 1.0,
|
||
sample_rate: int = 44100) -> bytes:
|
||
"""
|
||
生成测试音调(正弦波)
|
||
|
||
Args:
|
||
frequency: 频率 (Hz)
|
||
duration: 持续时间 (秒)
|
||
sample_rate: 采样率
|
||
|
||
Returns:
|
||
音频数据字节
|
||
"""
|
||
import math
|
||
import struct
|
||
|
||
# 生成正弦波数据
|
||
num_samples = int(duration * sample_rate)
|
||
audio_data = bytearray()
|
||
|
||
for i in range(num_samples):
|
||
# 生成正弦波样本
|
||
t = i / sample_rate
|
||
sample = math.sin(2 * math.pi * frequency * t)
|
||
|
||
# 转换为16位整数
|
||
sample_int = int(sample * 32767)
|
||
audio_data.extend(struct.pack('<h', sample_int))
|
||
|
||
return bytes(audio_data)
|
||
|
||
|
||
def convert_to_mono(audio_data: bytes, channels: int = 2) -> bytes:
|
||
"""
|
||
将多声道音频转换为单声道
|
||
|
||
Args:
|
||
audio_data: 音频数据
|
||
channels: 声道数
|
||
|
||
Returns:
|
||
单声道音频数据
|
||
"""
|
||
if channels == 1:
|
||
return audio_data
|
||
|
||
# 假设16位音频数据
|
||
sample_size = 2
|
||
frame_size = sample_size * channels
|
||
|
||
mono_data = bytearray()
|
||
for i in range(0, len(audio_data), frame_size):
|
||
# 取第一个声道的数据
|
||
mono_data.extend(audio_data[i:i+sample_size])
|
||
|
||
return bytes(mono_data)
|
||
|
||
|
||
def apply_volume_multiplier(audio_data: bytes, volume: float) -> bytes:
|
||
"""
|
||
应用音量乘数到音频数据
|
||
|
||
Args:
|
||
audio_data: 音频数据
|
||
volume: 音量乘数 (0.0-1.0)
|
||
|
||
Returns:
|
||
处理后的音频数据
|
||
"""
|
||
import struct
|
||
|
||
if volume == 1.0:
|
||
return audio_data
|
||
|
||
# 假设16位音频数据
|
||
sample_count = len(audio_data) // 2
|
||
processed_data = bytearray()
|
||
|
||
for i in range(sample_count):
|
||
# 解包16位样本
|
||
sample = struct.unpack_from('<h', audio_data, i * 2)[0]
|
||
|
||
# 应用音量
|
||
sample = int(sample * volume)
|
||
|
||
# 限制范围
|
||
sample = max(-32768, min(32767, sample))
|
||
|
||
# 重新打包
|
||
processed_data.extend(struct.pack('<h', sample))
|
||
|
||
return bytes(processed_data)
|
||
|
||
|
||
def calculate_doppler_shift(source_velocity: Tuple[float, float, float],
|
||
listener_velocity: Tuple[float, float, float],
|
||
sound_speed: float = 343.0) -> float:
|
||
"""
|
||
计算多普勒频移
|
||
|
||
Args:
|
||
source_velocity: 音源速度向量
|
||
listener_velocity: 听者速度向量
|
||
sound_speed: 声速 (米/秒)
|
||
|
||
Returns:
|
||
频率比率 (播放频率/原始频率)
|
||
"""
|
||
# 计算相对速度
|
||
rel_velocity_x = source_velocity[0] - listener_velocity[0]
|
||
rel_velocity_z = source_velocity[2] - listener_velocity[2]
|
||
|
||
# 计算速度大小
|
||
source_speed = math.sqrt(rel_velocity_x**2 + rel_velocity_z**2)
|
||
|
||
# 简化的多普勒效应计算
|
||
if sound_speed > source_speed:
|
||
ratio = (sound_speed - source_speed) / sound_speed
|
||
return max(0.5, min(2.0, ratio)) # 限制在合理范围内
|
||
else:
|
||
return 0.5 # 音源速度接近或超过声速时的处理
|
||
|
||
|
||
def create_hrtf_filter(angle: float, elevation: float) -> Dict[str, List[float]]:
|
||
"""
|
||
创建HRTF(头部相关传递函数)滤波器
|
||
|
||
Args:
|
||
angle: 水平角度 (度)
|
||
elevation: 垂直角度 (度)
|
||
|
||
Returns:
|
||
左右声道滤波器系数
|
||
"""
|
||
# 这是一个简化的HRTF实现
|
||
# 实际应用中会使用真实的HRTF测量数据
|
||
|
||
# 简单的左右声道延迟差异
|
||
angle_rad = math.radians(angle)
|
||
delay_diff = math.sin(angle_rad) * 0.001 # 最大1毫秒延迟差异
|
||
|
||
# 简单的左右声道幅度差异
|
||
left_amp = 1.0 - max(0, math.sin(angle_rad)) * 0.3
|
||
right_amp = 1.0 - max(0, -math.sin(angle_rad)) * 0.3
|
||
|
||
return {
|
||
'left': [left_amp, 0.0, 0.0],
|
||
'right': [right_amp, 0.0, delay_diff]
|
||
}
|
||
|
||
|
||
def calculate_reverb_parameters(room_size: Tuple[float, float, float],
|
||
absorption: float = 0.5) -> Dict[str, float]:
|
||
"""
|
||
根据房间尺寸计算混响参数
|
||
|
||
Args:
|
||
room_size: 房间尺寸 (长, 宽, 高)
|
||
absorption: 吸收系数 (0.0-1.0)
|
||
|
||
Returns:
|
||
混响参数字典
|
||
"""
|
||
length, width, height = room_size
|
||
|
||
# 计算房间体积
|
||
volume = length * width * height
|
||
|
||
# 计算表面积
|
||
surface_area = 2 * (length*width + length*height + width*height)
|
||
|
||
# 简化的混响时间计算(Sabine公式)
|
||
reverb_time = 0.161 * volume / (surface_area * absorption)
|
||
|
||
# 限制在合理范围内
|
||
reverb_time = max(0.1, min(10.0, reverb_time))
|
||
|
||
return {
|
||
'reverb_time': reverb_time,
|
||
'early_reflections': reverb_time * 0.1,
|
||
'late_reverb': reverb_time * 0.9,
|
||
'density': min(1.0, volume / 1000.0),
|
||
'diffusion': 1.0 - absorption
|
||
} |