Xisound 运行时模式(PC/DSP)切换规范 v1.0
文档定位
- 产生背景:2026-05-08 Source/Sink 重构发现智能体遗漏 PC/DSP 切换开关(Bug #7)· 现有所有文档 0 次提及此功能 · 本文档首次定义产品需求 + 实施规范
- 权威性:所有 apps(XiStudio / XiTune / XiTest / XiProbe)涉及运行时模式的 UI/数据流必须遵守本文档
- 上游:D3-FE-ARCH-001 顶层架构 · D3-FE-ARCH-007 模块 UI 实现规范 · Source/Sink Review v7.0
- 下游:各 app tech-arch 的 TopBar 设计 · WS 协议契约 · 后端 Runtime Mode Service
1. 为什么需要 PC/DSP 切换
1.1 用户场景
| 场景 | 运行时模式 | 用户意图 |
|---|---|---|
| 开发/调试算法 | PC 模拟 | 修改参数看效果 · 迭代快 · 无硬件也可运行 |
| 声学调音 | PC 模拟(桌面) | 用声卡模拟车载环境 · 方便 A/B 对比 |
| 量产测试 | DSP 硬件 | 真实 XiDSP + XiAmp + 车载声学环境 · 验证算法 AEC-Q100 合规 |
| 灯塔现场 | DSP 硬件 | 车辆交付前验收 · 不容许模拟 |
核心诉求:同一个工程文件(link.json)在 PC 和 DSP 两种运行模式下可切换执行,结果可对比。
1.2 三种运行模式
┌─────────────────────────────────────────────────────┐
│ PC 模式 (默认 · 开发态) │
│ ├── 前端 → C# 后端 → DSPAlgo.dll → WASAPI → 声卡 │
│ └── 特点:跨平台 · 开发友好 · 调音师桌面用 │
├─────────────────────────────────────────────────────┤
│ DSP 模式 (生产/测试) │
│ ├── 前端 → C# 后端 → 调试协议 → 真实 XiDSP → XiAmp → 车载 │
│ └── 特点:真实实时性 · AEC-Q100 合规 · 灯塔现场必需 │
├─────────────────────────────────────────────────────┤
│ 混合模式 (Y2+ · 可选) │
│ ├── 部分模块走 PC 模拟 + 部分走 DSP 硬件 │
│ └── 特点:前端实现成本高 · 暂缓到 Y2+ │
└─────────────────────────────────────────────────────┘
2. UI 位置与视觉规范(强制)
2.1 Shell 顶部栏位置
每个 app(主要是 XiStudio / XiTune)的 TopBar 必须在右侧用户头像左边放置一个运行时模式下拉:
┌─────────────────────────────────────────────────────────────────┐
│ Xisound Studio [工程名 · UnitTest v1.0] │
│ [▼ PC 模式] [👤 userA] [⚙] │ ← TopBar
└─────────────────────────────────────────────────────────────────┘
↑
运行时模式下拉(必须在此位置)
2.2 下拉内部结构
┌──────────────────────────────────┐
│ ● PC 模式(当前) │
│ 本地声卡 · 跨平台 │
├──────────────────────────────────┤
│ ○ DSP 硬件模式 │
│ 真实 XiDSP · 需连接硬件 │
│ [🔍 检测硬件] → 已连 XiDSP-D2 │
└──────────────────────────────────┘
[切换模式] [关闭]
必须元素:
| 元素 | 强制 | 说明 |
|---|---|---|
| 当前模式指示(下拉按钮文案) | ✅ | "PC 模式" / "DSP 硬件模式" · 带颜色 badge(PC=info-blue · DSP=warning-orange) |
| 两个选项的描述 | ✅ | 每个模式下方写一行不同点("本地声卡 · 跨平台" / "真实 XiDSP · 需连接硬件") |
| 硬件检测按钮(DSP 选项) | ✅ | 点击后后端扫描 · 显示检测结果("已连 XiDSP-D2" / "未检测到硬件") |
| 切换按钮(底部) | ✅ | 点击才真正切换 · 单选选项只是预选 |
| 加载中状态(切换时) | ✅ | 整个下拉变 disabled · 显示 spinner + "切换中..." |
2.3 切换时的视觉反馈
用户点"切换模式"后的视觉流程:
t=0ms: [▼ PC 模式] 点击
t+10ms: [▼ PC 模式 → 切换中...(spinner)]
t+1s: [▼ DSP 硬件模式(新状态)] · 右上角 Toast "已切换到 DSP 硬件模式"
· 如果切换失败 → Toast "切换失败:未检测到硬件" + 恢复原模式
3. 数据模型与状态机
3.1 useRuntimeModeStore
// stores/useRuntimeModeStore.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import { useAudioEngineStore } from './audioEngineStore'
export type RuntimeMode = 'pc' | 'dsp' | 'hybrid' // hybrid 留 Y2+
export type ModeSwitchStatus = 'idle' | 'detecting' | 'switching' | 'error'
export const useRuntimeModeStore = defineStore('runtime-mode', () => {
// 状态
const mode = ref<RuntimeMode>('pc') // 当前模式(默认 PC)
const switchStatus = ref<ModeSwitchStatus>('idle')
const lastError = ref<string | null>(null)
const hardwareDetected = ref<HardwareInfo | null>(null)
const supportedModes = ref<RuntimeMode[]>(['pc']) // 初始化后填充
// Getters
const isPC = computed(() => mode.value === 'pc')
const isDSP = computed(() => mode.value === 'dsp')
const canSwitchToDSP = computed(() => hardwareDetected.value !== null)
// Actions
async function detectHardware(): Promise<HardwareInfo | null> {
switchStatus.value = 'detecting'
try {
const info = await api.detectDSPHardware()
hardwareDetected.value = info
if (info) supportedModes.value.push('dsp')
return info
} catch (e) {
lastError.value = e.message
return null
} finally {
switchStatus.value = 'idle'
}
}
async function switchMode(targetMode: RuntimeMode): Promise<boolean> {
if (!supportedModes.value.includes(targetMode)) {
lastError.value = `模式 ${targetMode} 不受支持`
return false
}
if (mode.value === targetMode) return true // 同模式不切
switchStatus.value = 'switching'
try {
// 1. 暂停当前运行的链路(如果有)
const audioEngine = useAudioEngineStore()
await audioEngine.pauseLink()
// 2. 发送切换 WS 命令
await api.switchRuntimeMode(targetMode)
// 3. 重新 apply 当前 link(后端会根据 mode 路由到 PC DSP.dll 或真实硬件)
await audioEngine.applyLink()
// 4. 更新前端状态
mode.value = targetMode
lastError.value = null
return true
} catch (e) {
lastError.value = e.message
switchStatus.value = 'error'
return false
} finally {
if (switchStatus.value !== 'error') switchStatus.value = 'idle'
}
}
// 从 localStorage 恢复用户偏好
function restorePreference(): void {
const saved = localStorage.getItem('xi-runtime-mode')
if (saved === 'pc' || saved === 'dsp') mode.value = saved as RuntimeMode
}
// 持久化(模式改变时自动调用)
watch(mode, (v) => localStorage.setItem('xi-runtime-mode', v))
return { mode, switchStatus, lastError, hardwareDetected, supportedModes, isPC, isDSP, canSwitchToDSP, detectHardware, switchMode, restorePreference }
})
export interface HardwareInfo {
deviceType: 'XiDSP-D1' | 'XiDSP-D2' | 'XiDSP-D3' | 'XiDSP-D4' | 'XiDSP-A1'
serialNumber: string
firmwareVersion: string
sampleRate: number
channels: number
connectionType: 'usb' | 'uart' | 'network'
connectedAt: number
}
3.2 切换状态机
stateDiagram-v2
[*] --> PC_idle: 默认启动
PC_idle --> PC_detecting: 点击"检测硬件"
PC_detecting --> PC_idle: 未检测到
PC_detecting --> PC_ready_to_switch: 检测到硬件
PC_ready_to_switch --> PC_idle: 取消
PC_ready_to_switch --> Switching_to_DSP: 点击"切换模式"
Switching_to_DSP --> DSP_idle: 切换成功
Switching_to_DSP --> PC_idle: 切换失败 + Toast 错误
DSP_idle --> Switching_to_PC: 点击"切换模式" (选 PC)
Switching_to_PC --> PC_idle: 切换成功
Switching_to_PC --> DSP_idle: 切换失败
note right of Switching_to_DSP
1. 暂停当前链路
2. WS switch-runtime-mode
3. 后端 re-apply link
4. 前端刷新 audioEngine 状态
end note
4. 模式下的功能差异矩阵(产品必读)
4.1 关键差异
| 功能 | PC 模式 | DSP 硬件模式 | 备注 |
|---|---|---|---|
| 实时延迟 | ~10-30ms(WASAPI block=512) | < 2ms(XiDSP block=64/48kHz) | DSP 模式更接近车端 |
| 声卡/输出 | 本地 WASAPI 声卡(任意) | XiAmp + 车载音响 / 或外接 XiDSP 开发板 | DSP 不支持任意声卡 |
| source_wav | ✅ 完整支持 | ⚠️ 仅 AOT 编译后的 wav | DSP 不支持运行时换 wav |
| source_device | ✅ 任意 WASAPI 输入 | ✅ XiMic / XiCal 硬件直连 | DSP 模式下 source_device 对应硬件麦 |
| 参数热更新 | ✅ 无限制 | ✅ 但需受 DSP 内存限制 | DSP 有 MIPS/内存预算 |
| 模块数上限 | 无限(受 PC CPU) | 受 XiDSP 型号(D1:10 模块 / D4:100+) | DSP 模式会预警 |
| CPU 监控 | 前端显示 | DSP 侧采集回传 | Phase 8 §14 |
| 烧录功能 | ❌ 不适用 | ✅ 可进入烧录流程 | 仅 DSP 模式 |
| Signal Flow(CAN 信号) | ⚠️ 模拟信号 | ✅ 真实 CAN 总线 | DSP 模式对接真实车 |
4.2 切换时的用户提示
PC → DSP:
Toast (info): "已切换到 DSP 硬件模式"
- 硬件: XiDSP-D2 (v1.3.0)
- 采样率: 48000 Hz
- 当前链路: 8 模块 (预估 12% DSP 负载)
⚠️ 如果模块数超过硬件容量,会弹 Modal:
"当前链路 45 模块超过 XiDSP-D2 容量(30 模块)"
[查看详情] [返回 PC 模式]
DSP → PC:
Toast (info): "已切换到 PC 模式"
- 输出设备: [ASUS Xonar AE (当前默认)]
- 采样率: 48000 Hz
- source_device 模块数: 3(自动映射到本地麦)
5. WS 协议契约(@xi/protocol 新增)
5.1 消息 Schema
// packages/protocol/src/websocket/runtime-mode.ts
import { z } from 'zod'
// 前端 → 后端:切换模式
export const SwitchRuntimeModeCommandSchema = z.object({
type: z.literal('switch-runtime-mode'),
payload: z.object({
targetMode: z.enum(['pc', 'dsp', 'hybrid']),
preserveLink: z.boolean().default(true), // 是否保留当前 link
}),
})
// 前端 → 后端:检测硬件
export const DetectHardwareCommandSchema = z.object({
type: z.literal('detect-dsp-hardware'),
})
// 后端 → 前端:硬件检测结果
export const HardwareDetectedEventSchema = z.object({
type: z.literal('dsp-hardware-detected'),
payload: z.object({
detected: z.boolean(),
info: z.object({
deviceType: z.enum(['XiDSP-D1', 'XiDSP-D2', 'XiDSP-D3', 'XiDSP-D4', 'XiDSP-A1']),
serialNumber: z.string(),
firmwareVersion: z.string(),
sampleRate: z.number(),
channels: z.number(),
connectionType: z.enum(['usb', 'uart', 'network']),
}).nullable(),
}),
})
// 后端 → 前端:模式切换结果
export const RuntimeModeSwitchedEventSchema = z.object({
type: z.literal('runtime-mode-switched'),
payload: z.object({
success: z.boolean(),
newMode: z.enum(['pc', 'dsp']),
errorMessage: z.string().optional(),
linkStatus: z.object({
reapplied: z.boolean(),
moduleCount: z.number(),
estimatedLoad: z.number().optional(), // DSP 模式下 CPU 负载预估
}),
}),
})
5.2 HTTP API(辅助)
| 端点 | 方法 | 用途 |
|---|---|---|
/api/runtime/modes |
GET | 列出支持的运行时模式(基于当前硬件检测) |
/api/runtime/hardware |
GET | 查询当前硬件信息(缓存的) |
/api/runtime/mode |
GET | 查询当前运行时模式 |
/api/runtime/mode/switch |
POST | 切换模式(替代 WS,用于非实时场景) |
6. 与其他文档的集成
6.1 与 Source/Sink Review v7 的关系
- Source/Sink v7 描述了单一运行模式下的 DSP 实现细节(wav 解码器 / WASAPI capture / apply_link 等)
- 本规范补充:wav/device 在 PC 和 DSP 两种模式下的不同行为(§4.1)
- 不冲突:v7 仍然权威 · 本文档是前端 UI + 模式切换层的补充
6.2 与 ModuleNode 规范的关系
- D3-FE-ARCH-007 §2 规定 ModuleNode 的 7 个强制元素
- 本规范扩展:status 指示(§2.4)在 DSP 模式下额外支持
dsp-overload(红色闪烁)状态 - ModuleNode 可选显示 DSP 资源占用(右下角小字
3% CPU / 8KB)· Phase 8 后
6.3 与 Phase 8 Plan 的关系
- Phase 8 Plan 未体现 PC/DSP 切换(Phase 8 只聚焦后端+DSP 单路径)
- Phase 9 必须加入 PC/DSP 切换实施(前端 1 天 · 后端 Runtime Mode Service 3 天 · 测试 1 天 · 共 5 天)
7. 实施 checklist(给智能体)
7.1 前端实施(XiStudio)
- 新增
useRuntimeModeStore(§3.1 完整实现) - TopBar 加入运行时模式下拉(§2.1 位置)
- 下拉内部按 §2.2 渲染(两个选项 + 描述 + 检测按钮 + 切换按钮)
- 切换时的视觉反馈(§2.3 · spinner + disabled + Toast)
- localStorage 持久化用户偏好
- 切换失败时的 Toast + 恢复原状态
- 初始化时调用
restorePreference()+detectHardware()
7.2 后端实施(C# ASP.NET Core)
- 新增
RuntimeModeService(管理当前模式 + 硬件检测) - 实现
/api/runtime/*HTTP 端点 - WS handler 支持
switch-runtime-mode/detect-dsp-hardware命令 - 切换模式时:调用
DSPAlgo.ChangeRuntimeTarget(pc / dsp-device)· 重新apply_link - 发送
dsp-hardware-detected和runtime-mode-switched事件
7.3 DSP 侧实施
- XiDSP 固件支持硬件握手协议(USB/UART)
- 固件返回型号 + firmware 版本
- DSP 端 apply_link 协议与 PC 端一致(通过调试端口)
7.4 测试
- e2e:切换 PC → DSP → PC · 验证 3 模块链路无爆音
- e2e:DSP 模式下链路超载 · 验证 UI 提示
- e2e:切换失败时的 UI 恢复
- 单测:
useRuntimeModeStore状态机所有转换 - 压测:10 次快速切换 · 验证后端资源释放正确
8. DQ 清单
| DQ 编号 | 问题 | 建议 |
|---|---|---|
| DQ-MODE-01 | 是否所有 apps(XiTune / XiTest / XiProbe)都需要切换开关? | XiStudio / XiTune 必须 · XiTest 继承 · XiProbe 特殊(硬件测试本来就连 DSP) |
| DQ-MODE-02 | 混合模式(hybrid · 部分模块 PC + 部分 DSP)何时实现? | Y2+ · 需要更复杂的路由 · 留做 PLAN-012 |
| DQ-MODE-03 | 切换时是否保留 preset 状态? | ✅ 保留 · preset 是 param 集合 · 两种模式下 param schema 相同 |
| DQ-MODE-04 | 切换是否影响 Signal Flow 画布 bindings? | 不影响 · bindings 在两种模式下都生效 · 只是 DSP 模式下用真实 CAN |
| DQ-MODE-05 | XiDSP 硬件离线如何处理? | 前端保持 DSP 模式 · 底栏显示"硬件已离线 · 请检查连接" · 禁止应用新 link |
9. 版本与变更记录
| 版本 | 日期 | 作者 | 说明 |
|---|---|---|---|
| v1.0 | 2026-05-09 | work-cline | 初稿 · 首次定义 PC/DSP 切换产品需求 · TopBar 位置 · useRuntimeModeStore · WS 协议 · 模式差异矩阵 · 实施 checklist |
附录 A · 引用
上游: - D3-FE-ARCH-001 顶层架构 - D3-FE-ARCH-007 模块 UI 实现规范 - Source/Sink Review v7.0
同级: - D3-FE-ARCH-009 Preset 全链路规范(preset 跨模式保留)
下游: - D2-P1 XiStudio tech-arch:TopBar 实施应用本规范 - Phase 9 实施计划:PC/DSP 切换作为独立 plan 列入 D3-FE-PLAN-012