703 lines
24 KiB
Python
703 lines
24 KiB
Python
"""
|
|
音频触觉反馈模块
|
|
负责将音频特征转换为触觉效果
|
|
"""
|
|
|
|
import time
|
|
from typing import Dict, Any, List, Optional
|
|
import math
|
|
|
|
class AudioHaptics:
|
|
"""
|
|
音频触觉反馈
|
|
负责将音频特征转换为触觉效果
|
|
"""
|
|
|
|
def __init__(self, plugin):
|
|
"""
|
|
初始化音频触觉反馈
|
|
|
|
Args:
|
|
plugin: 触觉反馈系统插件实例
|
|
"""
|
|
self.plugin = plugin
|
|
self.enabled = False
|
|
self.initialized = False
|
|
|
|
# 音频分析配置
|
|
self.audio_config = {
|
|
'enable_audio_haptics': True,
|
|
'analysis_rate': 30.0, # Hz
|
|
'frequency_bands': 8,
|
|
'min_frequency': 20.0,
|
|
'max_frequency': 20000.0,
|
|
'intensity_scale': 1.0,
|
|
'frequency_scale': 1.0,
|
|
'smoothing_factor': 0.3
|
|
}
|
|
|
|
# 频带映射配置
|
|
self.band_mapping = {
|
|
'sub_bass': {'range': (20, 60), 'haptic_freq': 20.0, 'intensity': 1.0},
|
|
'bass': {'range': (60, 250), 'haptic_freq': 30.0, 'intensity': 0.9},
|
|
'low_mids': {'range': (250, 500), 'haptic_freq': 40.0, 'intensity': 0.7},
|
|
'mids': {'range': (500, 2000), 'haptic_freq': 50.0, 'intensity': 0.5},
|
|
'high_mids': {'range': (2000, 4000), 'haptic_freq': 60.0, 'intensity': 0.3},
|
|
'presence': {'range': (4000, 6000), 'haptic_freq': 70.0, 'intensity': 0.2},
|
|
'brilliance': {'range': (6000, 12000), 'haptic_freq': 80.0, 'intensity': 0.1},
|
|
'air': {'range': (12000, 20000), 'haptic_freq': 90.0, 'intensity': 0.05}
|
|
}
|
|
|
|
# 节拍检测配置
|
|
self.beat_config = {
|
|
'enable_beat_detection': True,
|
|
'min_bpm': 60,
|
|
'max_bpm': 180,
|
|
'sensitivity': 0.7,
|
|
'history_size': 30
|
|
}
|
|
|
|
# 音频特征配置
|
|
self.feature_config = {
|
|
'enable_volume_haptics': True,
|
|
'enable_spectral_haptics': True,
|
|
'enable_temporal_haptics': True,
|
|
'enable_rhythm_haptics': True
|
|
}
|
|
|
|
# 状态跟踪
|
|
self.audio_state = {
|
|
'last_analysis_time': 0.0,
|
|
'current_features': {},
|
|
'beat_history': [],
|
|
'intensity_history': []
|
|
}
|
|
|
|
# 设备映射
|
|
self.device_mapping = {
|
|
'low_frequencies': 'body', # 低频到身体设备
|
|
'mid_frequencies': 'hands', # 中频到手部设备
|
|
'high_frequencies': 'fingers' # 高频到手指设备
|
|
}
|
|
|
|
# 统计信息
|
|
self.stats = {
|
|
'audio_frames_analyzed': 0,
|
|
'haptic_effects_generated': 0,
|
|
'beats_detected': 0,
|
|
'spectral_events': 0
|
|
}
|
|
|
|
# 回调函数
|
|
self.audio_callbacks = {
|
|
'audio_analyzed': [],
|
|
'beat_detected': [],
|
|
'feature_extracted': [],
|
|
'haptic_effect_generated': []
|
|
}
|
|
|
|
print("✓ 音频触觉反馈已创建")
|
|
|
|
def initialize(self) -> bool:
|
|
"""
|
|
初始化音频触觉反馈
|
|
|
|
Returns:
|
|
是否初始化成功
|
|
"""
|
|
try:
|
|
self.initialized = True
|
|
print("✓ 音频触觉反馈初始化完成")
|
|
return True
|
|
|
|
except Exception as e:
|
|
print(f"✗ 音频触觉反馈初始化失败: {e}")
|
|
import traceback
|
|
traceback.print_exc()
|
|
return False
|
|
|
|
def enable(self) -> bool:
|
|
"""
|
|
启用音频触觉反馈
|
|
|
|
Returns:
|
|
是否启用成功
|
|
"""
|
|
try:
|
|
if not self.initialized:
|
|
print("✗ 音频触觉反馈未初始化")
|
|
return False
|
|
|
|
self.enabled = True
|
|
print("✓ 音频触觉反馈已启用")
|
|
return True
|
|
|
|
except Exception as e:
|
|
print(f"✗ 音频触觉反馈启用失败: {e}")
|
|
import traceback
|
|
traceback.print_exc()
|
|
return False
|
|
|
|
def disable(self):
|
|
"""禁用音频触觉反馈"""
|
|
try:
|
|
self.enabled = False
|
|
print("✓ 音频触觉反馈已禁用")
|
|
|
|
except Exception as e:
|
|
print(f"✗ 音频触觉反馈禁用失败: {e}")
|
|
import traceback
|
|
traceback.print_exc()
|
|
|
|
def finalize(self):
|
|
"""清理音频触觉反馈资源"""
|
|
try:
|
|
self.disable()
|
|
self.audio_state.clear()
|
|
self.audio_callbacks.clear()
|
|
self.initialized = False
|
|
print("✓ 音频触觉反馈资源已清理")
|
|
|
|
except Exception as e:
|
|
print(f"✗ 音频触觉反馈资源清理失败: {e}")
|
|
import traceback
|
|
traceback.print_exc()
|
|
|
|
def update(self, dt: float):
|
|
"""
|
|
更新音频触觉反馈状态
|
|
|
|
Args:
|
|
dt: 时间增量
|
|
"""
|
|
try:
|
|
if not self.enabled:
|
|
return
|
|
|
|
# 检查是否需要进行音频分析
|
|
current_time = time.time()
|
|
analysis_interval = 1.0 / self.audio_config['analysis_rate']
|
|
|
|
if current_time - self.audio_state['last_analysis_time'] >= analysis_interval:
|
|
# 模拟音频分析
|
|
self._analyze_audio_frame()
|
|
self.audio_state['last_analysis_time'] = current_time
|
|
|
|
except Exception as e:
|
|
print(f"✗ 音频触觉反馈更新失败: {e}")
|
|
import traceback
|
|
traceback.print_exc()
|
|
|
|
def _analyze_audio_frame(self):
|
|
"""分析音频帧并生成触觉效果"""
|
|
try:
|
|
if not self.enabled or not self.audio_config['enable_audio_haptics']:
|
|
return
|
|
|
|
# 模拟音频特征提取
|
|
audio_features = self._extract_audio_features()
|
|
|
|
# 更新当前特征
|
|
self.audio_state['current_features'] = audio_features
|
|
|
|
# 触发音频分析回调
|
|
self._trigger_audio_callback('audio_analyzed', {
|
|
'features': audio_features,
|
|
'timestamp': time.time()
|
|
})
|
|
|
|
# 处理音频特征并生成触觉效果
|
|
self._process_audio_features(audio_features)
|
|
|
|
# 检测节拍
|
|
if self.beat_config['enable_beat_detection']:
|
|
self._detect_beat(audio_features)
|
|
|
|
self.stats['audio_frames_analyzed'] += 1
|
|
|
|
except Exception as e:
|
|
print(f"✗ 音频帧分析失败: {e}")
|
|
|
|
def _extract_audio_features(self) -> Dict[str, Any]:
|
|
"""
|
|
提取音频特征
|
|
|
|
Returns:
|
|
音频特征字典
|
|
"""
|
|
try:
|
|
# 模拟音频特征提取
|
|
features = {
|
|
'timestamp': time.time(),
|
|
'rms': 0.5 + 0.5 * math.sin(time.time()), # 模拟音量
|
|
'spectral_centroid': 5000.0, # 模拟频谱质心
|
|
'spectral_rolloff': 10000.0, # 模拟频谱滚降点
|
|
'zero_crossing_rate': 0.1, # 模拟过零率
|
|
'temporal_flux': 0.3, # 模拟时间通量
|
|
'bpm': 120.0 # 模拟节拍
|
|
}
|
|
|
|
# 生成频带能量(模拟)
|
|
band_energies = []
|
|
for i in range(self.audio_config['frequency_bands']):
|
|
energy = 0.3 + 0.7 * math.sin(time.time() + i * 0.5)
|
|
band_energies.append(max(0.0, min(1.0, energy)))
|
|
|
|
features['band_energies'] = band_energies
|
|
|
|
# 触发特征提取回调
|
|
self._trigger_audio_callback('feature_extracted', {
|
|
'features': features
|
|
})
|
|
|
|
return features
|
|
|
|
except Exception as e:
|
|
print(f"✗ 音频特征提取失败: {e}")
|
|
return {}
|
|
|
|
def _process_audio_features(self, features: Dict[str, Any]):
|
|
"""
|
|
处理音频特征并生成触觉效果
|
|
|
|
Args:
|
|
features: 音频特征字典
|
|
"""
|
|
try:
|
|
if not features:
|
|
return
|
|
|
|
# 处理频带能量
|
|
if self.feature_config['enable_spectral_haptics']:
|
|
self._process_spectral_features(features)
|
|
|
|
# 处理音量
|
|
if self.feature_config['enable_volume_haptics']:
|
|
self._process_volume_features(features)
|
|
|
|
# 处理时间特征
|
|
if self.feature_config['enable_temporal_haptics']:
|
|
self._process_temporal_features(features)
|
|
|
|
except Exception as e:
|
|
print(f"✗ 音频特征处理失败: {e}")
|
|
|
|
def _process_spectral_features(self, features: Dict[str, Any]):
|
|
"""
|
|
处理频谱特征并生成触觉效果
|
|
|
|
Args:
|
|
features: 音频特征字典
|
|
"""
|
|
try:
|
|
band_energies = features.get('band_energies', [])
|
|
|
|
for i, energy in enumerate(band_energies):
|
|
if energy > 0.1: # 能量阈值
|
|
# 获取频带信息
|
|
band_names = list(self.band_mapping.keys())
|
|
if i < len(band_names):
|
|
band_name = band_names[i]
|
|
band_info = self.band_mapping[band_name]
|
|
|
|
# 计算触觉效果参数
|
|
intensity = energy * band_info['intensity'] * self.audio_config['intensity_scale']
|
|
frequency = band_info['haptic_freq'] * self.audio_config['frequency_scale']
|
|
duration = 0.05 + energy * 0.1 # 基于能量的持续时间
|
|
|
|
# 确定目标设备
|
|
device_type = self._map_frequency_to_device(band_info['haptic_freq'])
|
|
device_id = self._get_device_id_by_type(device_type)
|
|
|
|
# 生成触觉效果
|
|
self._generate_haptic_effect(
|
|
effect_type='periodic',
|
|
device_id=device_id,
|
|
intensity=intensity,
|
|
frequency=frequency,
|
|
duration=duration,
|
|
parameters={
|
|
'source': 'spectral',
|
|
'band': band_name,
|
|
'energy': energy
|
|
}
|
|
)
|
|
|
|
self.stats['spectral_events'] += 1
|
|
|
|
except Exception as e:
|
|
print(f"✗ 频谱特征处理失败: {e}")
|
|
|
|
def _process_volume_features(self, features: Dict[str, Any]):
|
|
"""
|
|
处理音量特征并生成触觉效果
|
|
|
|
Args:
|
|
features: 音频特征字典
|
|
"""
|
|
try:
|
|
rms = features.get('rms', 0.0)
|
|
|
|
if rms > 0.05: # 音量阈值
|
|
# 计算触觉效果参数
|
|
intensity = rms * self.audio_config['intensity_scale']
|
|
frequency = 20.0 + rms * 30.0 # 基于音量的频率
|
|
duration = 0.02 # 短持续时间以保持响应性
|
|
|
|
# 生成触觉效果
|
|
self._generate_haptic_effect(
|
|
effect_type='periodic',
|
|
device_id=None, # 所有设备
|
|
intensity=intensity,
|
|
frequency=frequency,
|
|
duration=duration,
|
|
parameters={
|
|
'source': 'volume',
|
|
'rms': rms
|
|
}
|
|
)
|
|
|
|
except Exception as e:
|
|
print(f"✗ 音量特征处理失败: {e}")
|
|
|
|
def _process_temporal_features(self, features: Dict[str, Any]):
|
|
"""
|
|
处理时间特征并生成触觉效果
|
|
|
|
Args:
|
|
features: 音频特征字典
|
|
"""
|
|
try:
|
|
temporal_flux = features.get('temporal_flux', 0.0)
|
|
|
|
if temporal_flux > 0.2: # 变化阈值
|
|
# 计算触觉效果参数
|
|
intensity = temporal_flux * self.audio_config['intensity_scale']
|
|
frequency = 40.0 + temporal_flux * 40.0
|
|
duration = 0.03 + temporal_flux * 0.07
|
|
|
|
# 生成触觉效果
|
|
self._generate_haptic_effect(
|
|
effect_type='noise',
|
|
device_id=None,
|
|
intensity=intensity,
|
|
frequency=frequency,
|
|
duration=duration,
|
|
parameters={
|
|
'source': 'temporal',
|
|
'flux': temporal_flux
|
|
}
|
|
)
|
|
|
|
except Exception as e:
|
|
print(f"✗ 时间特征处理失败: {e}")
|
|
|
|
def _detect_beat(self, features: Dict[str, Any]):
|
|
"""
|
|
检测节拍并生成触觉效果
|
|
|
|
Args:
|
|
features: 音频特征字典
|
|
"""
|
|
try:
|
|
if not self.beat_config['enable_beat_detection']:
|
|
return
|
|
|
|
# 添加到节拍历史
|
|
timestamp = features.get('timestamp', time.time())
|
|
rms = features.get('rms', 0.0)
|
|
|
|
self.audio_state['beat_history'].append({
|
|
'timestamp': timestamp,
|
|
'energy': rms
|
|
})
|
|
|
|
# 保持历史记录大小
|
|
if len(self.audio_state['beat_history']) > self.beat_config['history_size']:
|
|
self.audio_state['beat_history'].pop(0)
|
|
|
|
# 检测节拍(简化实现)
|
|
if len(self.audio_state['beat_history']) > 2:
|
|
current_energy = self.audio_state['beat_history'][-1]['energy']
|
|
previous_energy = self.audio_state['beat_history'][-2]['energy']
|
|
|
|
# 简单的能量峰值检测
|
|
if current_energy > previous_energy * (1.0 + self.beat_config['sensitivity']):
|
|
# 生成节拍触觉效果
|
|
self._generate_haptic_effect(
|
|
effect_type='impulse',
|
|
device_id=None,
|
|
intensity=current_energy * self.audio_config['intensity_scale'],
|
|
frequency=60.0,
|
|
duration=0.05,
|
|
parameters={
|
|
'source': 'beat',
|
|
'energy': current_energy
|
|
}
|
|
)
|
|
|
|
# 触发节拍检测回调
|
|
self._trigger_audio_callback('beat_detected', {
|
|
'timestamp': timestamp,
|
|
'energy': current_energy
|
|
})
|
|
|
|
self.stats['beats_detected'] += 1
|
|
|
|
except Exception as e:
|
|
print(f"✗ 节拍检测失败: {e}")
|
|
|
|
def _map_frequency_to_device(self, frequency: float) -> str:
|
|
"""
|
|
将频率映射到设备类型
|
|
|
|
Args:
|
|
frequency: 频率值
|
|
|
|
Returns:
|
|
设备类型
|
|
"""
|
|
try:
|
|
if frequency < 40.0:
|
|
return 'body'
|
|
elif frequency < 80.0:
|
|
return 'hands'
|
|
else:
|
|
return 'fingers'
|
|
except Exception as e:
|
|
print(f"✗ 频率到设备映射失败: {e}")
|
|
return 'hands'
|
|
|
|
def _get_device_id_by_type(self, device_type: str) -> Optional[int]:
|
|
"""
|
|
根据设备类型获取设备ID
|
|
|
|
Args:
|
|
device_type: 设备类型
|
|
|
|
Returns:
|
|
设备ID或None
|
|
"""
|
|
try:
|
|
if not self.plugin.device_manager:
|
|
return None
|
|
|
|
# 查找匹配类型的设备
|
|
devices = self.plugin.device_manager.get_devices_by_type(device_type)
|
|
if devices:
|
|
return devices[0]['id'] # 返回第一个匹配的设备
|
|
|
|
return None
|
|
except Exception as e:
|
|
print(f"✗ 设备ID获取失败: {e}")
|
|
return None
|
|
|
|
def _generate_haptic_effect(self, effect_type: str, device_id: Optional[int],
|
|
intensity: float, frequency: float, duration: float,
|
|
parameters: Dict[str, Any]) -> bool:
|
|
"""
|
|
生成触觉效果
|
|
|
|
Args:
|
|
effect_type: 效果类型
|
|
device_id: 设备ID
|
|
intensity: 强度
|
|
frequency: 频率
|
|
duration: 持续时间
|
|
parameters: 参数
|
|
|
|
Returns:
|
|
是否生成成功
|
|
"""
|
|
try:
|
|
if not self.plugin.haptic_manager:
|
|
return False
|
|
|
|
# 限制参数范围
|
|
intensity = max(0.0, min(1.0, intensity))
|
|
frequency = max(1.0, min(100.0, frequency))
|
|
duration = max(0.01, min(2.0, duration))
|
|
|
|
# 生成触觉效果
|
|
effect_id = self.plugin.haptic_manager.play_effect(
|
|
effect_type=effect_type,
|
|
device_id=device_id,
|
|
intensity=intensity,
|
|
duration=duration,
|
|
frequency=frequency,
|
|
parameters=parameters
|
|
)
|
|
|
|
if effect_id >= 0:
|
|
# 触发效果生成回调
|
|
self._trigger_audio_callback('haptic_effect_generated', {
|
|
'effect_id': effect_id,
|
|
'effect_type': effect_type,
|
|
'intensity': intensity,
|
|
'frequency': frequency,
|
|
'duration': duration,
|
|
'parameters': parameters
|
|
})
|
|
|
|
self.stats['haptic_effects_generated'] += 1
|
|
return True
|
|
|
|
return False
|
|
|
|
except Exception as e:
|
|
print(f"✗ 触觉效果生成失败: {e}")
|
|
return False
|
|
|
|
def _trigger_audio_callback(self, callback_type: str, data: Dict[str, Any]):
|
|
"""
|
|
触发音频回调
|
|
|
|
Args:
|
|
callback_type: 回调类型
|
|
data: 回调数据
|
|
"""
|
|
try:
|
|
if callback_type in self.audio_callbacks:
|
|
for callback in self.audio_callbacks[callback_type]:
|
|
try:
|
|
callback(data)
|
|
except Exception as e:
|
|
print(f"✗ 音频回调执行失败: {e}")
|
|
except Exception as e:
|
|
print(f"✗ 音频回调触发失败: {e}")
|
|
|
|
def register_audio_callback(self, callback_type: str, callback: callable):
|
|
"""
|
|
注册音频回调
|
|
|
|
Args:
|
|
callback_type: 回调类型
|
|
callback: 回调函数
|
|
"""
|
|
try:
|
|
if callback_type in self.audio_callbacks:
|
|
self.audio_callbacks[callback_type].append(callback)
|
|
print(f"✓ 音频回调已注册: {callback_type}")
|
|
else:
|
|
print(f"✗ 无效的回调类型: {callback_type}")
|
|
except Exception as e:
|
|
print(f"✗ 音频回调注册失败: {e}")
|
|
|
|
def unregister_audio_callback(self, callback_type: str, callback: callable):
|
|
"""
|
|
注销音频回调
|
|
|
|
Args:
|
|
callback_type: 回调类型
|
|
callback: 回调函数
|
|
"""
|
|
try:
|
|
if callback_type in self.audio_callbacks:
|
|
if callback in self.audio_callbacks[callback_type]:
|
|
self.audio_callbacks[callback_type].remove(callback)
|
|
print(f"✓ 音频回调已注销: {callback_type}")
|
|
except Exception as e:
|
|
print(f"✗ 音频回调注销失败: {e}")
|
|
|
|
def set_audio_config(self, config: Dict[str, Any]) -> bool:
|
|
"""
|
|
设置音频配置
|
|
|
|
Args:
|
|
config: 配置字典
|
|
|
|
Returns:
|
|
是否设置成功
|
|
"""
|
|
try:
|
|
self.audio_config.update(config)
|
|
print(f"✓ 音频配置已更新: {self.audio_config}")
|
|
return True
|
|
except Exception as e:
|
|
print(f"✗ 音频配置设置失败: {e}")
|
|
return False
|
|
|
|
def set_band_mapping(self, mapping: Dict[str, Dict[str, Any]]) -> bool:
|
|
"""
|
|
设置频带映射
|
|
|
|
Args:
|
|
mapping: 映射字典
|
|
|
|
Returns:
|
|
是否设置成功
|
|
"""
|
|
try:
|
|
self.band_mapping.update(mapping)
|
|
print(f"✓ 频带映射已更新")
|
|
return True
|
|
except Exception as e:
|
|
print(f"✗ 频带映射设置失败: {e}")
|
|
return False
|
|
|
|
def set_beat_config(self, config: Dict[str, Any]) -> bool:
|
|
"""
|
|
设置节拍检测配置
|
|
|
|
Args:
|
|
config: 配置字典
|
|
|
|
Returns:
|
|
是否设置成功
|
|
"""
|
|
try:
|
|
self.beat_config.update(config)
|
|
print(f"✓ 节拍检测配置已更新: {self.beat_config}")
|
|
return True
|
|
except Exception as e:
|
|
print(f"✗ 节拍检测配置设置失败: {e}")
|
|
return False
|
|
|
|
def get_audio_config(self) -> Dict[str, Any]:
|
|
"""
|
|
获取音频配置
|
|
|
|
Returns:
|
|
配置字典
|
|
"""
|
|
return self.audio_config.copy()
|
|
|
|
def get_band_mapping(self) -> Dict[str, Dict[str, Any]]:
|
|
"""
|
|
获取频带映射
|
|
|
|
Returns:
|
|
映射字典
|
|
"""
|
|
return self.band_mapping.copy()
|
|
|
|
def get_beat_config(self) -> Dict[str, Any]:
|
|
"""
|
|
获取节拍检测配置
|
|
|
|
Returns:
|
|
配置字典
|
|
"""
|
|
return self.beat_config.copy()
|
|
|
|
def get_stats(self) -> Dict[str, int]:
|
|
"""
|
|
获取统计信息
|
|
|
|
Returns:
|
|
统计信息字典
|
|
"""
|
|
return self.stats.copy()
|
|
|
|
def reset_stats(self):
|
|
"""重置统计信息"""
|
|
try:
|
|
self.stats = {
|
|
'audio_frames_analyzed': 0,
|
|
'haptic_effects_generated': 0,
|
|
'beats_detected': 0,
|
|
'spectral_events': 0
|
|
}
|
|
print("✓ 音频触觉反馈统计信息已重置")
|
|
except Exception as e:
|
|
print(f"✗ 音频触觉反馈统计信息重置失败: {e}") |