""" 音频编辑器 提供可视化编辑音频场景和参数的功能 """ from typing import Dict, List, Any, Optional from panda3d.core import NodePath, Vec3 class AudioEditor: """ 音频编辑器 提供可视化编辑音频场景和参数的功能 """ def __init__(self, plugin, world): """ 初始化音频编辑器 Args: plugin: 3D音频插件实例 world: 3D世界实例 """ self.plugin = plugin self.world = world self.visible = False self.editor_node = None self.zone_visualizations = {} self.sound_visualizations = {} self.selected_item = None # 编辑器状态 self.edit_mode = False self.selected_tool = "select" # select, add_sound, add_zone self.grid_size = 5.0 self.snap_to_grid = True # GUI元素 self.gui_elements = [] self.tool_buttons = {} def toggle_visibility(self): """切换编辑器可见性""" if self.visible: self.hide() else: self.show() def show(self): """显示编辑器""" if self.visible: return self.visible = True self._create_editor_visualization() self._create_editor_gui() print("✓ 音频编辑器已显示") def hide(self): """隐藏编辑器""" if not self.visible: return self.visible = False self._cleanup_editor_visualization() self._cleanup_editor_gui() print("✓ 音频编辑器已隐藏") def _create_editor_visualization(self): """创建编辑器可视化元素""" try: if not self.world.render: return # 创建编辑器根节点 self.editor_node = self.world.render.attachNewNode("audio_editor") # 可视化现有的音频区域 self._visualize_zones() # 可视化现有的音频源 self._visualize_sounds() except Exception as e: print(f"⚠ 编辑器可视化创建失败: {e}") def _cleanup_editor_visualization(self): """清理编辑器可视化元素""" try: # 清理区域可视化 for zone_node in self.zone_visualizations.values(): zone_node.removeNode() self.zone_visualizations.clear() # 清理音源可视化 for sound_node in self.sound_visualizations.values(): sound_node.removeNode() self.sound_visualizations.clear() # 清理编辑器根节点 if self.editor_node: self.editor_node.removeNode() self.editor_node = None except Exception as e: print(f"⚠ 编辑器可视化清理失败: {e}") def _visualize_zones(self): """可视化音频区域""" if not self.editor_node: return # 获取所有区域信息 zones_info = self.plugin.environment_audio_manager.get_all_zones_info() for zone_info in zones_info: zone_id = zone_info['id'] position = zone_info['position'] radius = zone_info['radius'] # 创建区域可视化节点 zone_node = self.editor_node.attachNewNode(f"zone_{zone_id}") zone_node.setPos(position[0], position[1], position[2]) # 这里应该创建一个球体或其他可视化表示 # 为简化起见,我们只是存储节点 self.zone_visualizations[zone_id] = zone_node def _visualize_sounds(self): """可视化音频源""" if not self.editor_node: return # 获取所有音频源 for sound_id, sound_info in self.plugin.audio_source_manager.audio_sources.items(): position = sound_info['position'] # 创建音源可视化节点 sound_node = self.editor_node.attachNewNode(f"sound_{sound_id}") sound_node.setPos(position[0], position[1], position[2]) # 这里应该创建一个图标或其他可视化表示 # 为简化起见,我们只是存储节点 self.sound_visualizations[sound_id] = sound_node def _create_editor_gui(self): """创建编辑器GUI""" try: if not hasattr(self.world, 'gui_manager') or not self.world.gui_manager: print("⚠ GUI管理器不可用,无法创建编辑器界面") return gui_manager = self.world.gui_manager # 创建编辑器面板标题 panel = gui_manager.createGUIButton( pos=(0.7, 0, 0.95), text="音频编辑器", size=0.06 ) self.gui_elements.append(panel) # 创建工具按钮 tools = [ ("选择", "select"), ("添加音效", "add_sound"), ("添加区域", "add_zone") ] for i, (name, tool) in enumerate(tools): button = gui_manager.createGUIButton( pos=(0.7, 0, 0.88 - i * 0.06), text=name, size=0.04 ) self.gui_elements.append(button) self.tool_buttons[tool] = button self.world.accept(f"audio_tool_{tool}", lambda t=tool: self.set_edit_tool(t)) # 创建编辑功能按钮 functions = [ ("网格对齐", self.toggle_grid_snap), ("播放测试音", self.play_test_sound), ("环境音效", self.toggle_environment_audio), ("保存场景", self.save_audio_scene), ("加载场景", self.load_audio_scene) ] for i, (name, callback) in enumerate(functions): button = gui_manager.createGUIButton( pos=(0.82, 0, 0.88 - i * 0.06), text=name, size=0.04 ) self.gui_elements.append(button) self.world.accept(f"audio_func_{name}", callback) except Exception as e: print(f"⚠ 编辑器GUI创建失败: {e}") def _cleanup_editor_gui(self): """清理编辑器GUI""" try: if hasattr(self.world, 'gui_manager') and self.world.gui_manager: gui_manager = self.world.gui_manager for element in self.gui_elements: gui_manager.deleteGUIElement(element) # 清理事件监听 tools = ["select", "add_sound", "add_zone"] for tool in tools: self.world.ignore(f"audio_tool_{tool}") functions = [ "网格对齐", "播放测试音", "环境音效", "保存场景", "加载场景" ] for func in functions: self.world.ignore(f"audio_func_{func}") self.gui_elements.clear() self.tool_buttons.clear() except Exception as e: print(f"⚠ 编辑器GUI清理失败: {e}") def set_edit_tool(self, tool: str): """设置编辑工具""" self.selected_tool = tool print(f"✓ 切换到 {tool} 工具") # 更新按钮状态 for tool_name, button in self.tool_buttons.items(): if tool_name == tool: # 高亮选中工具 button.setColor(0.5, 1.0, 0.5, 1.0) # 绿色 else: # 恢复默认颜色 button.setColor(1.0, 1.0, 1.0, 1.0) # 白色 def toggle_grid_snap(self): """切换网格对齐""" self.snap_to_grid = not self.snap_to_grid print(f"✓ 网格对齐已{'启用' if self.snap_to_grid else '禁用'}") def play_test_sound(self): """播放测试音效""" print("✓ 播放测试音效") def toggle_environment_audio(self): """切换环境音频""" self.plugin.environment_audio_manager.toggle_enabled() print("✓ 环境音频切换") def save_audio_scene(self): """保存音频场景""" print("✓ 保存音频场景") def load_audio_scene(self): """加载音频场景""" print("✓ 加载音频场景") def handle_mouse_click(self, mouse_x: float, mouse_y: float): """处理鼠标点击事件""" if not self.edit_mode: return # 将屏幕坐标转换为世界坐标 world_pos = self._screen_to_world(mouse_x, mouse_y) if not world_pos: return # 根据当前工具执行操作 if self.selected_tool == "add_sound": self._add_sound_at_position(world_pos) elif self.selected_tool == "add_zone": self._add_zone_at_position(world_pos) elif self.selected_tool == "select": self._select_item_at_position(world_pos) def _screen_to_world(self, screen_x: float, screen_y: float) -> Optional[Vec3]: """ 将屏幕坐标转换为世界坐标 Args: screen_x: 屏幕X坐标 (-1 到 1) screen_y: 屏幕Y坐标 (-1 到 1) Returns: 世界坐标,如果转换失败则返回None """ # 这是一个简化的实现 # 实际应用中需要使用摄像机的射线检测 # 简单地将屏幕坐标映射到XZ平面 x = screen_x * 20 # 假设场景大小为40x40 z = screen_y * 20 y = 0 # 在地面高度 # 网格对齐 if self.snap_to_grid: x = round(x / self.grid_size) * self.grid_size z = round(z / self.grid_size) * self.grid_size return Vec3(x, y, z) def _add_sound_at_position(self, position: Vec3): """ 在指定位置添加音效 Args: position: 位置 """ print(f"✓ 在位置 {position} 添加音效") # 这里应该打开一个对话框让用户选择音频文件等参数 def _add_zone_at_position(self, position: Vec3): """ 在指定位置添加区域 Args: position: 位置 """ print(f"✓ 在位置 {position} 添加音频区域") # 这里应该打开一个对话框让用户设置区域参数 def _select_item_at_position(self, position: Vec3): """ 选择指定位置的项目 Args: position: 位置 """ print(f"✓ 选择位置 {position} 的项目") # 这里应该实现项目选择逻辑 def toggle_edit_mode(self): """切换编辑模式""" self.edit_mode = not self.edit_mode if self.edit_mode: print("✓ 进入音频编辑模式") else: print("✓ 退出音频编辑模式") def update(self, dt: float): """更新编辑器状态""" # 处理键盘输入 if self.world.mouseWatcherNode.hasMouse(): mouse_pos = self.world.mouseWatcherNode.getMouse() # 检查鼠标按键 from panda3d.core import MouseWatcher if self.world.mouseWatcherNode.isButtonDown(MouseWatcher.getGlobalPointer()): self.handle_mouse_click(mouse_pos.x, mouse_pos.y) # 检查键盘快捷键 from panda3d.core import KeyboardButton if self.world.mouseWatcherNode.isButtonDown(KeyboardButton.asciiKey('e')): self.toggle_edit_mode() def cleanup(self): """清理编辑器资源""" self.hide() self.plugin = None self.world = None self.selected_item = None class AudioSceneSerializer: """ 音频场景序列化器 处理音频场景的保存和加载 """ def __init__(self, plugin): """ 初始化序列化器 Args: plugin: 3D音频插件实例 """ self.plugin = plugin def save_scene(self, filepath: str) -> bool: """ 保存音频场景 Args: filepath: 文件路径 Returns: 是否保存成功 """ try: import json # 收集场景数据 scene_data = { 'version': '1.0', 'zones': self._collect_zones_data(), 'sounds': self._collect_sounds_data(), 'effects': self._collect_effects_data() } # 保存到文件 with open(filepath, 'w', encoding='utf-8') as f: json.dump(scene_data, f, indent=2, ensure_ascii=False) print(f"✓ 音频场景已保存到: {filepath}") return True except Exception as e: print(f"✗ 保存音频场景失败: {e}") return False def load_scene(self, filepath: str) -> bool: """ 加载音频场景 Args: filepath: 文件路径 Returns: 是否加载成功 """ try: import json # 从文件加载 with open(filepath, 'r', encoding='utf-8') as f: scene_data = json.load(f) # 应用场景数据 self._apply_zones_data(scene_data.get('zones', [])) self._apply_sounds_data(scene_data.get('sounds', [])) self._apply_effects_data(scene_data.get('effects', [])) print(f"✓ 音频场景已从 {filepath} 加载") return True except Exception as e: print(f"✗ 加载音频场景失败: {e}") return False def _collect_zones_data(self) -> List[Dict[str, Any]]: """收集区域数据""" zones_data = [] zones_info = self.plugin.environment_audio_manager.get_all_zones_info() for zone_info in zones_info: zone_id = zone_info['id'] # 获取区域的完整信息 zone = self.plugin.environment_audio_manager.zones[zone_id] zone_data = { 'id': zone_id, 'position': zone['position'], 'radius': zone['radius'], 'properties': zone['properties'], 'active': zone['active'] } zones_data.append(zone_data) return zones_data def _collect_sounds_data(self) -> List[Dict[str, Any]]: """收集音效数据""" sounds_data = [] for sound_id, sound_info in self.plugin.audio_source_manager.audio_sources.items(): sound_data = { 'id': sound_id, 'filepath': sound_info['filepath'], 'type': sound_info['type'], 'position': sound_info['position'], 'loop': sound_info['loop'], 'volume': sound_info['volume'] } sounds_data.append(sound_data) return sounds_data def _collect_effects_data(self) -> List[Dict[str, Any]]: """收集效果数据""" effects_data = [] for effect_id, effect_info in self.plugin.audio_effect_processor.effects.items(): effect_data = { 'id': effect_id, 'type': effect_info['type'], 'parameters': effect_info['parameters'], 'active': effect_info['active'] } effects_data.append(effect_data) return effects_data def _apply_zones_data(self, zones_data: List[Dict[str, Any]]): """应用区域数据""" for zone_data in zones_data: zone_id = zone_data['id'] position = zone_data['position'] radius = zone_data['radius'] properties = zone_data['properties'] # 创建区域 self.plugin.environment_audio_manager.create_zone( zone_id, position, radius, properties) # 设置活动状态 if not zone_data.get('active', True): self.plugin.environment_audio_manager.disable_zone(zone_id) def _apply_sounds_data(self, sounds_data: List[Dict[str, Any]]): """应用音效数据""" for sound_data in sounds_data: sound_id = sound_data['id'] filepath = sound_data['filepath'] sound_type = sound_data['type'] position = sound_data['position'] loop = sound_data['loop'] volume = sound_data['volume'] # 创建音效 if sound_type == '3d': new_sound_id = self.plugin.audio_source_manager.create_3d_sound( filepath, position, loop, volume) else: new_sound_id = self.plugin.audio_source_manager.create_2d_sound( filepath, loop, volume) # 注意:由于ID可能已更改,需要处理关联关系 if new_sound_id: # 播放音效 self.plugin.audio_source_manager.play_sound(new_sound_id) def _apply_effects_data(self, effects_data: List[Dict[str, Any]]): """应用效果数据""" for effect_data in effects_data: effect_id = effect_data['id'] effect_type = effect_data['type'] parameters = effect_data['parameters'] # 创建效果 new_effect_id = self.plugin.audio_effect_processor.create_effect( effect_type, parameters) # 设置活动状态 if not effect_data.get('active', True): if new_effect_id: self.plugin.audio_effect_processor.disable_effect(new_effect_id)