SUPERSEDED
ADR-AIOS-17 · XiStudio Realtime IO Architecture v2
状态:accepted v0.2 · 2026-06-04 15:18 · 业务行为契约 5 必填段 × 4 块全部填齐 · 7 fork 解锁派发
触发:用户 2026-06-04 12:37 反馈(stop H3 commit 900432b 时)· H1+H2+H3 三轮 hotfix 触底 · 仅修 RMS+FFT(7 widget 中 2 个)· 5 widget stub 占位 + loopback 完全不可用 + source UI 缺失 · 经评估为架构级需求 · 不能再 hotfix
拍板:用户 12:39 路径 A 起 ADR · 13:52 accept v0.1 · 15:18 拍板选项 B 升级 v0.2(收窄 Realtime stage · 新起 ADR-18 做 XiLink-Analyze stage · log module / RMS / scope 底座固化在本 ADR §3.4)
1. 上下文(Context)
1.1 用户原话(verbatim · 防信息漂移 · 两轮存档)
1.1.1 第一轮原话(2026-06-04 12:37 · 4 大需求拍板基准)
stop P0.A13.H3-realtime-widgets-and-loopback-source commit 900432b
但是问题并没有解决,目前只有 fft 和 rms 有效果,其他都没实装,感觉是之前的决议中没有彻底执行完,很多都显示 phase2 占位,phase 等没信号;
关于 loopback 的问题:再说一下策略:
参考 APX 的工作原理
output configuration:connector:下拉框可选 Windows/mac/linux Audio,ASIO extern
Device 下拉框:分别对应的是 connector 下的设备列表,ASIO connector 下可以特别说一下 XiStudio loopback 是我们产品内部的回环结构,剩下的根据设备驱动来识别别入 asio4all,ASIO MADIface USB,ASIO A2B,ASIO HDSPe AoX 等等
同 output 的结构,input 也一样,是输入测量信号的设备,只不 connector 如果是 xiprobe 或者 xical 的话,可以选择 line input 直接测量
重点说一下 loopback 模式,当 output 和 input 都是 loopback xistudio 模式的清空下,output 的信号直接回环到 input,这样所有的信号检测完全是软件的 source sink 在工作,那就需要有一个类似 generator 的界面来配置信号,这个界面当打开 loopback 的模式下出现,可以将 sourcev1 的内容显示出来,通道数量等等自动适配,此处可以考虑将调音工具的所有接口开放出来,甚至可以考虑将 xilink 的工程直接导入,并且将 xitune 的界面也可以调用出来,进行直接更改参数,看结果;
按照 APX500 的结构 loopback 模式只会换到输入,但是无法监听,我们的 loopback 需要做到既可以 loopback 也可以通过硬件设备进行监听,这样就需要开放 sink 界面,同时可以对 sink 的输入节点做数据监听;
这个就引申出下一个功能,需要有一个类似高通 QXDM 抓取音频数据 log 的模块,插入在链路不同的位置,可以抓取当前界面的 pcm 数据,也可以和上层互动实时回传数据;给后续的实时显示 module 做为基础使用;
关于右侧的 rms 和 fft phase 等插件的输入,需要做设备接入的端点选择,这样在 xilink 和 xituen 中可以任意切换检测的位置;
这是基本功能插件的所有能力,这些为后续 xilink xitune xitest 中的 integration 等搭好基础;
1.1.2 第二轮原话(2026-06-04 15:02 · 关键术语 + 设计修订 + stage 拆分提议)
块1:我倾向于后者(更简洁:xiprobe = ASIO driver · device 下拉显示该 driver 暴露的 input 端点列表 · 含 line input 端点)。
块2:疑问a和b:tap 应该是xilink内的独立module,可以取名log,rms meter目前没有,计划要有的module;rms meter module和xitest 没有任何关系,他是一个单独的界面,可以直接弹窗显示rms,同样的module还有scope可以实时显示波形等内容;log module就是我们说的xitest需要用到的endpoint,一个链路中可以添加N个log module,再loopback 或者xilink导入的清空下,默认肯定需要能够监听sink之前的数据强制必须,如果通路中有log module,则可以选择对应的不同名字的logmodule进行不同节点的数据检测;说清楚你疑问b关于rms meter和tap的关系了吧
疑问c:DSP落盘按照logmodule的名字来输出音频包(有可能的话加上文字log,对比QXDM);可以按照一定的周期滚顶显示;
块3:已经在2中解答了
新增问题:按照这个逻辑拆分下来,加载xilink这个显示实时显示不同节点的数据和log已经成为了高通的QXDM的工具了,那我们是否可以拆分一下功能,在realtime前加一个button,主要就是分析xilink的音频流,realtime中主要还是针对输入输出设备的实时曲线显示,和提供APx500专业测量的基本信号生成对比链路;
1.1.3 第三轮拍板(2026-06-04 15:18 · v0.2 范围决议)
- (b) 选项 B:ADR-17 收窄到 Realtime stage · 新起 ADR-18 承载 XiLink-Analyze stage
- (是) RMS meter / scope module schema 固定在 ADR-17 §3.4 底座 · ADR-18 上层只编排
- (是) F7 e2e 收窄到 Realtime stage · xilink-analyze 由 ADR-18 自带 e2e
1.2 历史教训(为什么需要 ADR · 不能再 hotfix)
| 阶段 |
范围 |
结果 |
教训 |
| H1 (bcd9a74 · 6 commits) |
5 议题 UX 修复 |
✅ 全闭环 |
UX 层面修复有效 |
| H2 (1325f8a · 1.5d) |
mic 链路诊断 + RMS+FFT 数据流 |
⚠️ 仅 2/7 widget |
验收 e2e 漏 12 cell · ask_followup 步骤被跳过 |
| H3 (900432b · 1.5~2.0d) |
5 子任务 + 14 cell e2e 全矩阵 |
⚠️ 触底 仍 2/7 widget |
5 widget 是 stub 占位 · loopback 真正缺失的是底层 IO 架构 · hotfix 解决不了 |
| 架构教训 |
架构级需求必须先 ADR 固化业务行为契约 + IO 架构 + 数据流路径 · 再 fork 分派任务 |
|
|
| 范围教训(v0.2) |
单 ADR 不要承载两种心智模型(APX500 设备测量 vs QXDM 链路分析)· 强行揉进等于业务契约 5 段 × 8-10 块 工作量爆炸 · 内部割裂 |
|
|
1.3 当前架构缺失盘点(v0.2 修订 · 6 项)
- IO 架构层:
- 当前 EnginePanel § Device 选择只有"硬件设备列表"扁平 · 无 connector 概念
- 缺 ASIO driver 识别(asio4all / MADIface USB / A2B / HDSPe AoX 等多 ASIO 驱动并存)
- 缺 XiStudio loopback 内部回环作为 ASIO 子选项
- input 端缺 xiprobe(= ASIO driver · device 下拉显该 driver 暴露的 input 端点列表 · 含 line input)/ xical connector
- Loopback 数据流层:
- 当前 mode='loopback' 标记存在但 backend 实际未切换路径(BuiltinLinkRegistry chain 未启)
- 缺软件 source generator 界面(sine/pink/wav 等参数化)
- 缺 sink 监听(loopback 时仍可硬件耳机监听)
- 缺 sink-pre 默认强制数据监听(loopback / xilink 导入下)
- Log module 层(v0.2 重构 · 原"audio tap"概念升级):
- log module 是 xilink chain 内独立 plugin(不是 service-level tap)· 一条链路可插 N 个 · 命名唯一
- DSP 端按 logmodule 名字落盘音频包(.pcm + .log.txt 双文件)· 滚动 polling 显示(类 QXDM)
- 完全缺失 · 当前 xilink chain plugin 体系无此 module 类型
- Widget endpoint 4 类选项 + 独立显示 module(v0.2 重构):
- 当前 RMS/FFT/Phase 等 widget 硬绑 sink-pre / physical-input 2 个 MeterNode 类型(ADR-12 §3 + ADR-7 §2.1.2)
- 缺 Widget endpoint 4 大类选择器(physical-input / sink-pre / log-module-by-name / xitune-module-port)
- 缺 RMS meter / scope 独立弹窗 module(xilink chain 内独立 plugin · 与 xitest 老路径 MeterTapService 完全正交 · 直接弹窗显示 · 同类还有未来扩展)
- 生态互通层:
- 缺 xilink 工程导入到 loopback generator
- 缺 xitune 界面在 loopback 模式下 inline 调用
- 🆕 Realtime stage 与 XiLink-Analyze stage 心智混淆(v0.2 新增):
- 当前单"Realtime"按钮承载两种使用场景(设备实时曲线 + APX500 信号生成对比 vs xilink 音频流分析 / QXDM 工具)
- 用户 15:02 拍板拆分:Realtime stage button(本 ADR-17)+ XiLink-Analyze stage button(后续 ADR-18)
- 共享底座:本 ADR §3.3 log module schema + §3.4 RMS / scope display module schema + DSP 落盘协议
2. 决议(Decision · v0.2 · 范围收窄到 Realtime Stage)
┌────────────────── XiStudio Top Stage Switcher ──────────────────┐
│ │
│ [▶ Realtime Stage] | [🔬 XiLink-Analyze Stage] │
│ (本 ADR-17) (ADR-18 后续 · 复用本 ADR 底座) │
│ │
└──────────────────────────────────────────────────────────────────┘
│
▼ Realtime Stage 内部(ADR-17 §2.1)
┌──────────────────── Realtime Stage(本 ADR-17 范围)───────────────────┐
│ │
│ ┌─ Output Configuration ──────────┐ ┌─ Input Configuration ─────────┐ │
│ │ Connector: [▼] │ │ Connector: [▼] │ │
│ │ - Windows/Mac/Linux Audio │ │ - Windows/Mac/Linux Audio │ │
│ │ - ASIO extern │ │ - ASIO extern │ │
│ │ (output 不含 xiprobe/xical) │ │ - xiprobe (= ASIO driver) │ │
│ │ Device: [▼] (filtered) │ │ - xical (= ASIO driver) │ │
│ │ - XiStudio loopback (ASIO) │ │ Device: [▼] (filtered) │ │
│ │ - asio4all / MADIface / A2B │ │ - XiStudio loopback │ │
│ │ - HDSPe AoX / etc. │ │ - xiprobe input N (含 line) │ │
│ └─────────────────────────────────┘ └───────────────────────────────┘ │
│ │ ▲ │
│ ▼ │ │
│ ┌─ Loopback Mode Generator(双向流核心 · output+input 都为 loopback)──┐│
│ │ Source: [▼ sine/pink/white/sweep/wav] + freq/level/sweep ││
│ │ Channels: 自动适配 sink 通道数 ││
│ │ [📁 导入 .xilink 工程] (复用 ADR-08 链路) ││
│ │ [🎛️ XiTune inline 调用] (ADR-11 接口) ││
│ │ │ ││
│ │ ▼ 软件信号链路 ││
│ │ Source → 调音模块(可选 xitune)→ [log module N 个可选] → Sink ││
│ │ │ │ ││
│ │ ┌─────┴─────┐ │ ││
│ │ ▼ ▼ ▼ ││
│ │ Loopback Hardware Monitor sink-pre 强制 tap (默认) ││
│ │ (软件回环) (output 硬件监听 · 不阻断 loopback) ││
│ └──────────────────────────────────────────────────────────────────────┘│
│ │ │
│ ▼ │
│ ┌─ Right Widget Pane(7 widget × Endpoint Selector 4 大类)─────────────┐│
│ │ [RMS] [FFT] [Phase] [Transfer] [Waveform] [Electrical] [Recorder] ││
│ │ 每个 widget 顶部:[▼ Endpoint:] 4 大类选项 ││
│ │ ① physical-input(物理输入设备帧) ││
│ │ ② sink-pre(默认强制 · loopback/xilink 监听点) ││
│ │ ③ log-module/<name>(xilink chain 内 N 个 log module 任选) ││
│ │ ④ xitune-module/<id>/<port>(xitune 模块端口暴露) ││
│ └──────────────────────────────────────────────────────────────────────┘│
└────────────────────────────────────────────────────────────────────────────┘
┌──────── 共享底座(本 ADR-17 §3.3 + §3.4 提供 · ADR-18 复用)────────────────┐
│ │
│ Log Module Schema(xilink chain 内独立 plugin): │
│ config: { name, position, captureMode } │
│ DSP 落盘:<workspace>/logs/<logmodule-name>-<timestamp>.{pcm, log.txt} │
│ WS frame: log_module_v1 (pcm chunk + text log line) │
│ │
│ RMS Meter Module Schema(xilink chain 内独立 plugin · 弹窗 component): │
│ config: { name, sourceEndpoint, refreshFps } │
│ │
│ Scope Module Schema(xilink chain 内独立 plugin · 弹窗 component): │
│ config: { name, sourceEndpoint, timeWindow, latencyMs } │
│ │
│ ❗ 与 xitest 老路径(MeterTapService 30fps · ADR-12 §3 7 widget)正交不动 │
└──────────────────────────────────────────────────────────────────────────────┘
2.2 4 大需求块结构化(v0.2 修订 · Realtime Stage 范围)
| # |
需求块 |
范围(v0.2) |
依赖 |
估时 |
| ① |
APX500 风格 IO 双下拉 |
Connector + Device 两级下拉 · ASIO driver 识别 · XiStudio loopback 作为 ASIO 子项 · xiprobe / xical = ASIO driver(device 下拉显该 driver 暴露的 input 端点 · 含 line input) |
P5 backend audio device service · ASIO API 适配 · P0 EnginePanel UI |
3-4d |
| ② |
Loopback 双向流 + 软件 generator + 硬件监听 |
mode='loopback' 真启用 BuiltinLinkRegistry chain · source generator UI · sink 输出可选硬件监听 · sink-pre 默认强制数据监听(loopback / xilink 导入下) |
F1(IO 架构)+ P5 RealtimeSessionService 重构 · BuiltinLinkRegistry 扩展(d8f0677 基线)· P0 generator UI |
4-5d |
| ③ |
Log module 底座(v0.2 重构 · 替换 v0.1"QXDM tap") |
log module = xilink chain 内独立 plugin · 一条链路可插 N 个 · 命名唯一 · DSP 端按 logmodule 名字落盘 .pcm + .log.txt · WS frame log_module_v1 滚动 polling |
F2(loopback)+ P5 LogModuleService NEW · P_contracts protocol 扩展(log_module_v1)· xilink chain plugin 注册扩展 |
2-3d |
| ④ |
Widget endpoint 4 类 + RMS/scope 独立弹窗 module 底座(v0.2 重构) |
Widget endpoint 4 大类选择器 · RMS meter / scope module schema 固定(xilink chain 内独立 plugin · 弹窗 component)· 不扩 ADR-7/12 老 MeterNode 枚举 · ADR-18 后续复用此底座 |
F1+F2+F3 全部 · P0 K-shared-meter-dock 加 endpoint selector · xilink chain plugin 注册 RMS/scope module type |
3-4d |
| 合计(Realtime stage) |
|
|
|
12-16d ≈ 1.5-2 周 |
v0.2 范围声明:
- ✅ 本 ADR-17 做:Realtime stage UI(顶部 button)+ §3.1-§3.4 4 块业务契约 + log module / RMS / scope module schema 底座
- ❌ 本 ADR-17 不做:XiLink-Analyze stage UI / .xilink 加载与节点 polling 编排 / xilink-analyze e2e(由 ADR-18 后续承载)
3. 业务行为契约(.clinerules v1.8 §"业务行为契约必填段"· 5 必填段 × 4 块全部填齐 · 对齐 ADR-12 §3 标杆)
✅ v0.2 完成版 · 4 块共 200+ 行 · 满足 .clinerules v1.8 派发自查清单 7 项 · 解锁 7 fork 派发硬阻塞
3.1 需求块 ① · APX500 风格 IO 双下拉架构
① 输入/输出契约
// 前端 TS 接口(P0)
interface IOConnectorOption {
id: 'windows-audio' | 'mac-audio' | 'linux-audio' | 'asio-extern' | 'xiprobe' | 'xical'
displayName: string // "Windows Audio" / "ASIO Extern" / "XiProbe (ASIO)"
supportedRoles: ('input' | 'output')[] // xiprobe/xical 只 input · windows-audio 双向
platform?: 'windows' | 'mac' | 'linux' // 平台过滤
}
interface IODeviceOption {
id: string // 后端唯一 device id
displayName: string // "XiStudio Loopback" / "ASIO MADIface USB" / "Line Input 1 (XiProbe)"
connectorId: string // 反查 connector
asioDriver?: string // ASIO driver name(asio4all / MADIface / A2B / HDSPe / xiprobe-driver)
isXiStudioLoopback?: boolean // 内部回环标记
isLineInput?: boolean // xiprobe/xical 暴露的 line input 端点
channels: number
sampleRate: number // 默认 48000
}
// 后端 REST(P5)
GET /api/audio/connectors → IOConnectorOption[]
GET /api/audio/devices?connector=<id>&role=<input|output> → IODeviceOption[]
② 收敛/成功判据
| 判据 |
阈值 |
含义 |
| connector 切换 → device 列表刷新 |
≤ 200ms |
UI 响应性 |
| ASIO + XiStudio loopback 选中 |
isXiStudioLoopback=true |
后端识别走内部回环路径 |
| xiprobe connector → device 列表 |
含 ≥ 1 条 isLineInput=true 项 |
line input 直接测量能力可达 |
| sample rate 协商 |
默认 48000Hz · 不匹配自动降级警告 |
设备适配 |
| connector + device 合法组合 |
output 拒绝 xiprobe/xical(supportedRoles 校验) |
role 防误选 |
| 设备热插拔 |
5s 内列表刷新 + 当前选项失效告警 |
鲁棒性 |
③ 失败回退路径(5 类)
| 失败 |
触发 |
UI 表现 |
恢复路径 |
| ASIO driver 未安装 |
GET /devices 返回空 |
"未检测到 ASIO 驱动 · 请安装 asio4all 或选 Windows Audio" |
自动回退 connector=windows-audio |
| device 占用(被其他 app 用) |
start 时 backend 报 device-busy |
toast "设备被占用 · 关闭其他应用后重试" |
不切设备 · 等用户手动重选 |
| 设备热拔出 |
运行中 device 消失 |
当前设备显红 + "设备已断开" + 自动暂停 session |
用户重选 device → ▶ 恢复 |
| sample rate 不匹配 |
选 device 后协商失败 |
warn "device 不支持 48000Hz · 已降级为 44100Hz" |
自动协商最近支持值 |
| connector 切换失败 |
role 校验失败(如 output 选 xiprobe) |
下拉禁选 + tooltip "xiprobe 仅支持 input role" |
阻止误选 · UI 灰显 |
④ 用户操作流(5 步)
| Step |
操作 |
UI 反馈 |
| 1 |
顶部点 [▶ Realtime Stage] button |
切到 Realtime stage · EnginePanel § Device 显示 |
| 2 |
Output Configuration → Connector 下拉选 "ASIO Extern" |
Device 下拉刷新 · 显 "XiStudio Loopback" + asio4all + ... |
| 3 |
Output Device 选 "XiStudio Loopback" |
下方出现 [Loopback Generator] 面板(等 input 也选 loopback 后激活) |
| 4 |
Input Configuration 选 connector=xiprobe + device=Line Input 1 |
双下拉绑定 realtimeRunStore · device 元信息存入 store |
| 5 |
点 ▶ 启动 |
session 启动 · sink-pre 帧到达 · RMS widget 显数值 |
⑤ 端到端真值 e2e(F7 必跑通)
// playwright e2e(F7 covered)
test('IO 双下拉 · 物理 mic + Windows Audio output → RMS 测量', async ({ page }) => {
await page.goto('/realtime')
await page.click('[data-test=output-connector]'); await page.click('text=Windows Audio')
await page.click('[data-test=output-device]'); await page.click('text=Default Speakers')
await page.click('[data-test=input-connector]'); await page.click('text=Windows Audio')
await page.click('[data-test=input-device]'); await page.click('text=Default Microphone')
// 注入物理 1kHz @ -20dBFS sine 30s
await injectPhysicalSine({ freq: 1000, levelDbFs: -20, durationS: 30 })
await page.click('[data-test=start-realtime]')
await page.waitForTimeout(5000)
const rms = await page.locator('[data-test=widget-rms-value]').textContent()
expect(parseFloat(rms!)).toBeCloseTo(-20, 1) // ± 1dB
})
test('IO 双下拉 · ASIO loopback 路径切换', async ({ page }) => {
// 选 output+input 都为 ASIO Extern → XiStudio Loopback
// 验后端 mode='loopback' + BuiltinLinkRegistry chain 启用
// 验 sink-pre 帧到达 · RMS widget 显软件 source 信号值
})
3.2 需求块 ② · Loopback 双向流 + 软件 generator + 硬件监听
① 输入/输出契约
interface LoopbackConfig {
enabled: boolean // output+input 都选 XiStudio Loopback 时 true
source: SourceConfig // 软件 source 配置
sinkPreTapEnforced: true // 默认强制 sink-pre 监听(不可关)
hardwareMonitor?: { // 可选 · 硬件耳机监听
enabled: boolean
deviceId: string // 监听用 output device(独立于 loopback path)
levelDb: number // 监听音量
}
xilinkProjectImport?: string // 可选 · 导入 .xilink 工程路径(ADR-08)
xituneInlineCall?: { moduleId: string } // 可选 · inline 调 xitune 模块
}
interface SourceConfig {
type: 'sine' | 'pink' | 'white' | 'sweep' | 'wav'
channels: number // 自动 = sink 通道数
sine?: { freq: number; levelDbFs: number }
pink?: { levelDbFs: number; bandwidthHz?: number }
sweep?: { startHz: number; endHz: number; durationS: number; logScale: boolean }
wav?: { filePath: string; loop: boolean }
}
// 后端 REST(P5)
POST /api/realtime/start body: { mode: 'loopback', loopback: LoopbackConfig }
PUT /api/realtime/source body: SourceConfig // 运行中可改 source
PUT /api/realtime/hardware-monitor body: { ... } // 运行中切换硬件监听
② 收敛/成功判据
| 判据 |
阈值 |
含义 |
| loopback 模式 sink-pre 强制 tap 帧率 |
≥ 25fps |
用户必能监听到信号 |
| source generator 通道数自适配 |
= sink 通道数(协商失败拒启) |
通道一致性 |
| hardware monitor 不阻断 loopback |
loopback 路径 latency 增量 ≤ 5ms |
监听不影响测量 |
| xilink 工程导入 → BuiltinLinkRegistry 重建 chain |
≤ 500ms |
工程切换响应 |
| sine 1kHz @ -20dBFS source → RMS widget |
-20 ± 1 dB |
信号路径正确性核心判据 |
| xitune inline 调用 |
5s 内 xitune 界面浮层弹出 + 模块参数双向绑定 |
生态互通 |
③ 失败回退路径(5 类)
| 失败 |
触发 |
UI 表现 |
恢复路径 |
| source 配置不合法(freq=0 / levelDbFs > 0) |
POST /start 时 backend validation |
表单红框 + 提示具体字段 |
用户改值后重启 |
| sink 通道数与 source 不匹配 |
source channels=2 · sink=1 |
warn "通道数不匹配 · 自动 mixdown" + 用户可拒绝 |
用户改 source channels 或换 device |
| hardware monitor 设备占用 |
monitor device 被其他 app 占用 |
toast "监听设备被占用 · monitor 已禁用 · loopback 继续" |
仅监听失败 · 不影响 loopback 主路径 |
| xilink 工程文件损坏 |
导入 .xilink JSON parse 失败 |
error toast + 工程导入回滚 |
用户重选有效工程 · loopback 用默认 source |
| xitune 调用超时 |
inline 调用 5s 无响应 |
warn + 弹窗关闭 · loopback 继续 |
xitune 进程重启后用户重试 |
④ 用户操作流(5 步)
| Step |
操作 |
UI 反馈 |
| 1 |
Realtime stage · output+input 都选 XiStudio Loopback |
EnginePanel 显 [Loopback Generator] 面板 |
| 2 |
Generator 选 source.type=sine + freq=1000 + levelDbFs=-20 |
source 配置预览 + 通道数显 = sink 通道 |
| 3 |
(可选)勾 [硬件监听] + 选 monitor device=Default Speakers |
hardware monitor 启用提示 |
| 4 |
(可选)[导入 .xilink 工程] 或 [XiTune inline 调用] |
链路图刷新 · xitune 浮层弹出 |
| 5 |
▶ 启动 → 观察 RMS widget = -20±1dB · 耳机听到 1kHz sine |
收敛 · session running |
⑤ 端到端真值 e2e(F7 必跑通)
test('Loopback · sine 1kHz @ -20dBFS → RMS = -20±1dB', async ({ page }) => {
await selectLoopbackMode(page)
await page.fill('[data-test=source-freq]', '1000')
await page.fill('[data-test=source-level]', '-20')
await page.click('[data-test=start-realtime]')
await page.waitForTimeout(3000)
const rms = parseFloat(await page.locator('[data-test=widget-rms-value]').textContent()!)
expect(rms).toBeCloseTo(-20, 1)
})
test('Loopback + hardware monitor · monitor 不阻断 loopback', async ({ page }) => {
await selectLoopbackMode(page)
await enableHardwareMonitor(page, 'Default Speakers')
await page.click('[data-test=start-realtime]')
// 验 loopback 路径 latency 仍 < target + monitor 设备出声(人工 / 真值 mic 验)
})
3.3 需求块 ③ · Log module 底座(xilink chain 内独立 plugin · QXDM 风格落盘)
① 输入/输出契约
// xilink chain plugin schema 扩展(P5 + P_contracts)
interface LogModuleConfig {
type: 'log-module' // xilink plugin type 注册新增
name: string // chain 内唯一(如 "mic-tap-1" / "after-eq-tap")
position: 'after-source' | 'mid' | 'before-sink' | { afterPluginId: string }
captureMode: 'pcm-dump' | 'realtime-stream' | 'both'
pcmDump?: {
enabled: boolean
rotationPolicy: 'time-based' | 'size-based' // 滚动 polling
maxSizeMb?: number // size-based 阈值
maxDurationS?: number // time-based 阈值
}
realtimeStream?: {
enabled: boolean
framePolicy: { fps: number; chunkMs: number } // 默认 25fps · 40ms chunk
}
textLog?: { enabled: boolean; level: 'info' | 'debug' } // 类 QXDM 文字 log
}
// DSP 端落盘文件命名(workspace-relative)
// <workspace>/logs/<logmodule-name>-<YYYYMMDD-HHmmss>.pcm
// <workspace>/logs/<logmodule-name>-<YYYYMMDD-HHmmss>.log.txt
// WS frame protocol(P_contracts protocol-v1 扩展 log_module_v1)
interface LogModuleFrame {
v: 1
type: 'log_module_v1'
moduleName: string
timestamp: number // ms epoch
pcmChunk?: { sampleRate: number; channels: number; samples: Float32Array }
textLog?: { level: string; message: string }
}
② 收敛/成功判据
| 判据 |
阈值 |
含义 |
| log module 名字唯一 |
同 chain 不可重复(命名冲突拒接受配置) |
数据回查可定位 |
| pcm 落盘速率 |
≥ 1MB/min @ 48kHz/2ch · 不丢帧 |
DSP 端写盘性能 |
| 文字 log 时间戳精度 |
≤ 1ms |
与 pcm 帧对齐 |
| 滚动 polling 帧率 |
≥ 10fps(WS frame log_module_v1) |
UI 实时性 |
| 多 log module 同 chain |
独立编号 · 独立文件 · 独立 WS stream |
多检测点支持 |
| chain 切换中 log module 状态保留 |
重建 chain 时同名 log module 配置不丢 |
UX 一致性 |
③ 失败回退路径(5 类)
| 失败 |
触发 |
UI 表现 |
恢复路径 |
| logmodule 名字重复 |
同 chain 第 2 个 module 命名冲突 |
前端表单 inline 错 "名字已被使用" |
用户改名 · 拒接受配置 |
| 磁盘写满 |
DSP 端 fwrite 失败 |
toast "落盘失败 · 磁盘已满" + 自动停 pcm-dump · realtime-stream 继续 |
用户清磁盘后手动恢复 |
| WS stream 断 |
client → server WS 心跳超时 |
UI 显 "断连" · client 自动重连 5s 间隔 |
重连成功后 stream 自动恢复 · 落盘不受影响 |
| chain 切换中 log module 状态丢失 |
hot-swap chain 时 module 配置未持久化 |
warn "log module 配置已重置" · 用户需重配 |
走 ADR-15 workspace persistence(持久化) |
| 落盘文件损坏 |
写盘断电 / kill -9 |
文件不可读 |
跳过损坏文件 · 后续 polling 周期新建文件 |
④ 用户操作流(5 步)
| Step |
操作 |
UI 反馈 |
| 1 |
Realtime stage 进入 loopback 模式(或 xilink 工程导入) |
chain 编辑器可见 |
| 2 |
拖入 [log module] plugin 到 chain 中(after-source 位置) |
chain node 显 "log module · 未命名" |
| 3 |
双击 node 配置:name=mic-tap-1 + captureMode=both + textLog enabled |
配置保存 · node 显 "log module · mic-tap-1" |
| 4 |
▶ 启动 → 滚动 polling UI 出现(可选展开) |
显实时 pcm 波形预览 + 文字 log line(类 QXDM) |
| 5 |
(可选)停 session 后到 <workspace>/logs/ 看 mic-tap-1-20260604-150000.pcm + .log.txt 双文件 |
落盘验证 |
⑤ 端到端真值 e2e(F7 必跑通)
test('Log module · loopback + sine 1kHz · sink-pre log module 抓 pcm + log.txt', async ({ page }) => {
await selectLoopbackMode(page)
await addLogModule(page, { name: 'sink-pre-tap', position: 'before-sink', captureMode: 'both' })
await setSourceSine(page, 1000, -20)
await page.click('[data-test=start-realtime]')
await page.waitForTimeout(10_000) // 跑 10s
await page.click('[data-test=stop-realtime]')
// 验落盘文件
const logsDir = await getWorkspaceLogsDir()
const pcmFiles = await glob(`${logsDir}/sink-pre-tap-*.pcm`)
expect(pcmFiles.length).toBe(1)
// 验 pcm RMS = -20 ± 1dB
const pcmRms = await analyzePcmFile(pcmFiles[0])
expect(pcmRms.dbFs).toBeCloseTo(-20, 1)
// 验 log.txt 时间戳连续(无 gap > 1s)
const logFile = pcmFiles[0].replace('.pcm', '.log.txt')
const lines = await readLines(logFile)
expect(verifyTimestampContinuity(lines, { maxGapMs: 1000 })).toBe(true)
})
① 输入/输出契约
// Widget endpoint 4 大类(P0 + P_contracts)
type WidgetEndpoint =
| { type: 'physical-input'; deviceId: string } // 物理输入设备帧
| { type: 'sink-pre' } // 默认强制(loopback/xilink 监听点)
| { type: 'log-module'; name: string } // xilink chain 内 log module(§3.3)
| { type: 'xitune-module'; moduleId: string; portId: string } // xitune 模块端口(ADR-11)
// 每个 widget 顶部 endpoint 选择器配置
interface WidgetEndpointSelectorState {
current: WidgetEndpoint
available: WidgetEndpoint[] // 当前 chain 动态可选
}
// RMS Meter Module schema(xilink chain 内独立 plugin · 弹窗 component)
interface RMSMeterModuleConfig {
type: 'rms-meter-module'
name: string // chain 内唯一
sourceEndpoint: WidgetEndpoint // 数据源
refreshFps: number // 默认 30fps
windowMs: number // RMS 窗口长度(ms · 默认 100)
display: { popup: true; size: { w: 400; h: 200 } } // 弹窗 component
}
// Scope Module schema(xilink chain 内独立 plugin · 弹窗 component)
interface ScopeModuleConfig {
type: 'scope-module'
name: string // chain 内唯一
sourceEndpoint: WidgetEndpoint
timeWindowMs: number // 显示时间窗口(默认 100ms)
latencyTargetMs: number // 默认 50ms
display: { popup: true; size: { w: 600; h: 300 } }
}
// ❗ 与 ADR-12 §3 7 widget MeterTapService 30fps 老路径正交不动
// ADR-7 P0.K-shared-meter-dock 6 stub vue 不再扩 MeterNode 枚举
// Widget endpoint selector 是 widget 内部 source 切换 · 不改 dock 结构
② 收敛/成功判据
| 判据 |
阈值 |
含义 |
| endpoint 切换响应时间 |
≤ 200ms widget 数据源切到新节点 |
UI 响应 |
| sink-pre 默认强制 |
loopback/xilink 模式下 endpoint=sink-pre 不可空 · 不可禁 |
默认监听强制 |
| RMS meter module 弹窗刷新 |
≥ 30fps |
实时性 |
| Scope module 显波形 latency |
≤ 50ms |
波形抖动可接受 |
| 多个独立弹窗 module 同时打开 |
≥ 4 个不卡 · 内存占用 < 200MB |
多检测点能力 |
| endpoint 切换中数据帧顺序 |
不错乱 · 不重复 · 不丢帧 |
数据完整性 |
| xilink chain plugin 注册 RMS/scope module type |
xilink chain 编辑器拖盘可见 |
plugin 体系融入 |
③ 失败回退路径(5 类)
| 失败 |
触发 |
UI 表现 |
恢复路径 |
| endpoint 不存在(log module 已删 / xitune module 已卸) |
widget 选中 endpoint 在 chain 中找不到 |
widget 显 "endpoint 已失效" + 自动回退 sink-pre |
用户重选 endpoint |
| xitune module port 协议不匹配 |
endpoint=xitune-module 但 port 不暴露音频 |
widget 显 "端口不支持音频流" |
用户选其他 port |
| 弹窗 module 关闭后数据流泄漏 |
RMS meter 弹窗关 · backend stream 未停 |
(内部) 自动取消订阅 |
LogModuleService 自动 GC |
| 多 module 同时打开内存占用超限 |
8 个弹窗 module + 16 chain · OOM |
warn "已开启 module 过多 · 关闭部分" + 拒新开 |
用户关部分 |
| endpoint 切换中数据帧错乱 |
切换瞬间 stream 未排空 |
(内部) 切换时插入 reset frame · UI 显 "切换中..." 100ms |
100ms 后正常 |
④ 用户操作流(5 步 · 含两条主路径)
路径 A:Widget endpoint selector
| Step |
操作 |
UI 反馈 |
| 1 |
Realtime stage · loopback 模式 + 添加 log module mic-tap-1 in chain |
chain 编辑器显新 module |
| 2 |
右侧 widget pane 选 RMS widget 顶部 [▼ Endpoint] |
下拉显 4 大类:physical-input / sink-pre / log-module/mic-tap-1 / xitune-module/... |
| 3 |
选 log-module/mic-tap-1 |
widget 数据源 200ms 内切到 mic-tap-1 帧 |
| 4 |
数值切换显示新节点 RMS |
收敛 |
| 5 |
切回 sink-pre(默认) |
数值切换回默认监听 |
路径 B:RMS meter / scope 独立弹窗 module
| Step |
操作 |
UI 反馈 |
| 1 |
xilink chain 编辑器 → 拖入 [RMS Meter Module] plugin |
chain 显新 node |
| 2 |
配置 name=rms-after-eq + sourceEndpoint=log-module/mic-tap-1 |
配置保存 |
| 3 |
▶ session 启动 |
RMS meter 弹窗自动浮出(400×200)· 显实时数值 ≥ 30fps |
| 4 |
(可选)再拖入 [Scope Module] · 配置 name=scope-1 · sourceEndpoint=sink-pre |
scope 弹窗浮出(600×300)· 显波形 latency ≤ 50ms |
| 5 |
多弹窗共存 · 用户拖动布局 |
4 个独立 module 不卡顿 |
⑤ 端到端真值 e2e(F7 必跑通)
test('Widget endpoint · loopback + log module 切换 · RMS = -20±1dB', async ({ page }) => {
await selectLoopbackMode(page)
await addLogModule(page, { name: 'mid-tap', position: 'mid', captureMode: 'realtime-stream' })
await setSourceSine(page, 1000, -20)
await page.click('[data-test=start-realtime]')
// 默认 sink-pre · 验数值
const rmsDefault = parseFloat(await page.locator('[data-test=widget-rms-value]').textContent()!)
expect(rmsDefault).toBeCloseTo(-20, 1)
// 切到 log-module/mid-tap · 验数值
await page.click('[data-test=widget-rms-endpoint-selector]')
await page.click('text=log-module/mid-tap')
await page.waitForTimeout(300)
const rmsLog = parseFloat(await page.locator('[data-test=widget-rms-value]').textContent()!)
expect(rmsLog).toBeCloseTo(-20, 1)
})
test('RMS meter / scope 独立弹窗 module · 双显一致', async ({ page }) => {
await selectLoopbackMode(page)
await setSourceSine(page, 1000, -20)
await addRMSMeterModule(page, { name: 'rms-1', sourceEndpoint: { type: 'sink-pre' } })
await addScopeModule(page, { name: 'scope-1', sourceEndpoint: { type: 'sink-pre' } })
await page.click('[data-test=start-realtime]')
await page.waitForTimeout(3000)
// 验弹窗 RMS module 数值
const rmsPopup = parseFloat(await page.locator('[data-test=rms-module-popup-rms-1] [data-test=value]').textContent()!)
expect(rmsPopup).toBeCloseTo(-20, 1)
// 验弹窗 scope module 显波形(canvas pixel hash 不为空 + 周期性匹配 1kHz)
const scopeCanvas = page.locator('[data-test=scope-module-popup-scope-1] canvas')
const periodicityValid = await verifyScopePeriodicity(scopeCanvas, { freqHz: 1000, sampleRate: 48000 })
expect(periodicityValid).toBe(true)
})
4. 实施清单(Implementation · v1.9 fork 表 · v0.2 修订)
✅ v0.2 业务契约已填齐 · 7 fork 解锁派发 · UID 锁定 v1.9 命名铁律 P{N}.A17.F{M}-<slug>
| F# |
UID |
部门 |
CPU |
工作量 |
描述 |
| F1 |
P5.A17.F1-io-connector-device-api |
后端 |
ClaudeB |
2.0d |
AudioDeviceService 重构 · 新增 IConnectorRegistry · ASIO driver 识别(asio4all/MADIface/A2B/HDSPe + xiprobe/xical 作为 ASIO driver)· XiStudio loopback 注册为 ASIO 子项 · 2 REST endpoint(/api/audio/connectors + /api/audio/devices?connector=&role=)· 业务契约 §3.1 |
| F2 |
P0.A17.F2-engine-panel-io-dropdowns |
前端 |
ClaudeA |
1.5d |
EnginePanel.vue 重构 § Device 段 · Connector + Device 两级下拉(input + output 各一组)· useAudioDevices composable 扩展 · 双向绑定 realtimeRunStore · 业务契约 §3.1 操作流 5 步 |
| F3 |
P5.A17.F3-loopback-bidirectional-flow |
后端 |
ClaudeB |
2.0d |
RealtimeSessionService.Start mode='loopback' 真启用 BuiltinLinkRegistry chain · sink-pre 默认强制 tap · 同时支持 sink 输出硬件监听(不阻断 loopback)· xilink/xitune 互通接口 stub · 业务契约 §3.2 |
| F4 |
P0.A17.F4-loopback-generator-ui |
前端 |
ClaudeA |
2.0d |
Loopback mode 时显示 generator 面板 · source 类型选择(sine/pink/sweep/wav)+ 通道自适配 + xilink 工程导入按钮 + xitune inline 调用按钮 · 业务契约 §3.2 操作流 5 步 |
| F5 |
P5.A17.F5-log-module-and-display-module-infra |
后端 |
ClaudeB |
2.5d |
NEW LogModuleService · xilink chain plugin 注册 log-module / rms-meter-module / scope-module 三 type · DSP 端落盘协议(.pcm + .log.txt 双文件 · 滚动 polling)· P_contracts protocol-v1 扩展 log_module_v1 frame · ADR-18 复用底座 · 业务契约 §3.3 + §3.4 schema 部分 |
| F6 |
P0.A17.F6-widget-endpoint-selector-4types |
前端 |
ClaudeA |
1.5d |
7 widget 顶部加 Endpoint Selector(4 大类:physical-input/sink-pre/log-module/xitune-module)· P0.K-shared-meter-dock 加 endpoint state · RMS meter / scope 弹窗 component 实现(从 chain 拖入触发) · 业务契约 §3.4 操作流路径 A+B |
| F7 |
P_e2e.A17.F7-truth-e2e-realtime-stage |
测试编排 |
ClaudeC |
2.0d |
playwright e2e Realtime stage 全矩阵真值:7 widget × 2 mode(hardware/loopback)+ §3.1-§3.4 4 块业务契约 ⑤ 段 e2e 全跑 + RMS meter / scope 弹窗双显一致 · 真硬件 mic + 耳机 · 不含 xilink-analyze stage(由 ADR-18 自带 e2e) |
| 合计 |
|
|
|
13.5d ≈ 1.5-2 周(7 fork 跨栈) |
|
关键路径:F1 → F3 → F5 → F6 → F7(后端 IO → loopback flow → log module 底座 → widget endpoint UI → e2e)
并行路径:F2(前端 IO UI · 与 F1 文件正交)· F4(generator UI · 等 F2+F3)
Phase 派发计划:
- Phase 1(IO 架构层 · 关键路径起手):F1(ClaudeB 2.0d)+ F2(ClaudeA 1.5d)文件正交可并行
- Phase 2(loopback 真启用):F3(ClaudeB)+ F4(ClaudeA)
- Phase 3(log module + widget endpoint 底座):F5(ClaudeB 2.5d)+ F6(ClaudeA 1.5d)
- Phase 4(全矩阵 e2e):F7(ClaudeC 2.0d 收尾 🏆)
5. 与现有 ADR 关系(v0.2 更新 · 新增 ADR-18 互通)
| ADR |
关系 |
| ADR-AIOS-12(XiTest Realtime Arch · 7 widget 业务契约 · fulfilled 🏆) |
保持基线 · 本 ADR §3 业务契约对齐 ADR-12 §3 标杆 · 7 widget 行为契约不动 · MeterTapService 30fps 老路径正交不动 |
| ADR-AIOS-13(双模式 hardware/loopback) |
deferred-by-ADR-17 · 本 ADR 接力 · ADR-13 暂停 fulfilled 判定 · F8 e2e 是否继续待用户拍板 |
| ADR-AIOS-07(P0.K-shared-meter-dock · 6 stub vue + 6 类型) |
不扩 MeterNode 枚举(v0.2 修订)· 改为 widget 内置 endpoint selector(4 大类)· dock 结构不动 |
| ADR-AIOS-08(XiLink Stage UX) |
互通 · F4 generator 支持导入 .xilink 工程 · F5 log module 注册为 xilink chain plugin |
| ADR-AIOS-11(XiTune) |
互通 · F4 generator 支持 inline 调用 XiTune 模块编辑界面 |
| 🆕 ADR-AIOS-18(即将起草 · XiLink-Analyze Stage / QXDM-style 工具) |
复用本 ADR §3.3 + §3.4 底座 · 不重复造 log module / RMS meter / scope module schema · 仅做 .xilink 加载 + 节点 polling 编排 + 上层 stage UI · 估 3-4 fork × 7-8d ≈ 1.5 周 |
| ADR-AIOS-15(workspace persistence) |
互通 · log module 配置 + RMS/scope module 配置 走 workspace 持久化(满足 §3.3 ③ chain 切换状态保留判据) |
6. 风险与缓解
| 风险 |
缓解 |
| ASIO driver 多平台兼容(Windows/Mac/Linux ASIO ≠ 统一) |
F1 抽象 IConnectorRegistry · 各平台独立 driver adapter · 第一期仅支持 Windows ASIO + Windows Audio · Mac/Linux 留 stub 占位 |
| BuiltinLinkRegistry chain 性能(loopback + log module + RMS/scope module 并发实时回传) |
F3+F5+F6 性能基线测试 · 4 通道 48kHz 实时回传 < 50ms latency · 最多 4 并发弹窗 module 内存 < 200MB |
| Widget endpoint 4 类与 xilink/xitune 节点 enum 互通(跨进程 IPC) |
F6 走 P_contracts protocol-v1 扩展(log_module_v1 + xitune-port 协议)· 不引入新 IPC 通道 |
| 与 ADR-15 Phase 3 三 fork(F4/F5/F6)并发(ClaudeA/B 资源 · 由 aios-cpu1 推进) |
ADR-17 fork 派发等 ADR-15 Phase 3 收尾 · 文件正交避冲突 · DASHBOARD §🤔 持续监控 |
| 1.5-2 周大型 ADR · 中途需求漂移 |
v0.2 业务契约 5 段 × 4 块已锁死 · 中途需求改动须升级 ADR-17-R1 修订 |
| 🆕 ADR-18(XiLink-Analyze stage)起草节奏与 ADR-17 fork 派发节奏冲突 |
ADR-18 起草不阻塞 ADR-17 fork 派发 · ADR-17 F5 log module 底座完成后 ADR-18 才进入实施期(自然解耦) |
7. 状态流转
| 时间 |
事件 |
备注 |
| 2026-06-04 12:40 |
proposed v0.1-draft |
Cline-AIOS 起草 · 4 大需求结构化 + 7 fork 实施清单 + 业务契约 stub |
| 2026-06-04 13:52 |
accepted v0.1 |
用户拍板 v0.1 · fork 派发被 .clinerules v1.8 §业务契约硬阻塞 · 必须先升 v0.2 |
| 2026-06-04 14:08 |
PLAN 模式对齐 §3 业务契约填充内容 |
用户切 PLAN · 4 块业务参数 80% 对齐 |
| 2026-06-04 15:02 |
用户提关键术语修订 + stage 拆分提议 |
tap → log module · MeterNode 扩展 → Widget endpoint 4 类 · 拆 Realtime/XiLink-Analyze 两 stage |
| 2026-06-04 15:18 |
accepted v0.2 |
用户拍板 b/是/是 · ADR-17 收窄 Realtime stage · 新起 ADR-18 · §3 4 块业务契约全部填齐 · 7 fork 派发解锁 |
| (待) |
impl Phase 1 |
F1 + F2 (IO 架构层 · 关键路径起手) |
| (待) |
impl Phase 2 |
F3 + F4 (Loopback + generator) |
| (待) |
impl Phase 3 |
F5 + F6 (Log module 底座 + Widget endpoint) |
| (待) |
impl Phase 4 |
F7 e2e Realtime stage 全矩阵 |
| (待) |
fulfilled 🏆 |
7 fork 全 zombie · 7 widget × 2 mode 全矩阵 e2e 通过 · 用户验收 |
| (待 · ADR-18 起草) |
ADR-AIOS-18 proposed |
XiLink-Analyze stage / QXDM-style 工具 · 复用本 ADR §3.3 + §3.4 底座 |
8. 决议者签名
- proposed by:Cline-AIOS(2026-06-04 12:40 v0.1 / 2026-06-04 15:18 v0.2)
- review by:user(2026-06-04 14:08 PLAN 模式 4 块业务参数对齐 + 15:02 关键术语修订 + 15:18 b/是/是 三连拍板)
- accepted by:user(2026-06-04 15:18 v0.2)
- 后续修订:ADR-AIOS-17-R1(若中途需求漂移)/ ADR-AIOS-17-R2(若 7 widget 架构再升级)
- 后续起草:ADR-AIOS-18(XiLink-Analyze Stage · 复用本 ADR §3.3 + §3.4 底座)
下一步:Cline-AIOS 同步修订 DASHBOARD v4.0.9 → v4.0.10(7 fork blocked-by-v0.2 → ready) + P_arch/PROCESS.md(ADR-17 子事件 + 7 fork user_threads)+ P0-xishell/PROCESS.md(关键里程碑 v0.2 行)→ 用户复核后派发 Phase 1 F1+F2 关键路径起手(ClaudeB 2.0d + ClaudeA 1.5d 文件正交可并行)。