跳转至
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 项)

  1. IO 架构层:
  2. 当前 EnginePanel § Device 选择只有"硬件设备列表"扁平 · 无 connector 概念
  3. 缺 ASIO driver 识别(asio4all / MADIface USB / A2B / HDSPe AoX 等多 ASIO 驱动并存)
  4. 缺 XiStudio loopback 内部回环作为 ASIO 子选项
  5. input 端缺 xiprobe(= ASIO driver · device 下拉显该 driver 暴露的 input 端点列表 · 含 line input)/ xical connector
  6. Loopback 数据流层:
  7. 当前 mode='loopback' 标记存在但 backend 实际未切换路径(BuiltinLinkRegistry chain 未启)
  8. 缺软件 source generator 界面(sine/pink/wav 等参数化)
  9. 缺 sink 监听(loopback 时仍可硬件耳机监听)
  10. 缺 sink-pre 默认强制数据监听(loopback / xilink 导入下)
  11. Log module 层(v0.2 重构 · 原"audio tap"概念升级):
  12. log module 是 xilink chain 内独立 plugin(不是 service-level tap)· 一条链路可插 N 个 · 命名唯一
  13. DSP 端按 logmodule 名字落盘音频包(.pcm + .log.txt 双文件)· 滚动 polling 显示(类 QXDM)
  14. 完全缺失 · 当前 xilink chain plugin 体系无此 module 类型
  15. Widget endpoint 4 类选项 + 独立显示 module(v0.2 重构):
  16. 当前 RMS/FFT/Phase 等 widget 硬绑 sink-pre / physical-input 2 个 MeterNode 类型(ADR-12 §3 + ADR-7 §2.1.2)
  17. 缺 Widget endpoint 4 大类选择器(physical-input / sink-pre / log-module-by-name / xitune-module-port)
  18. RMS meter / scope 独立弹窗 module(xilink chain 内独立 plugin · 与 xitest 老路径 MeterTapService 完全正交 · 直接弹窗显示 · 同类还有未来扩展)
  19. 生态互通层:
  20. 缺 xilink 工程导入到 loopback generator
  21. 缺 xitune 界面在 loopback 模式下 inline 调用
  22. 🆕 Realtime stage 与 XiLink-Analyze stage 心智混淆(v0.2 新增):
  23. 当前单"Realtime"按钮承载两种使用场景(设备实时曲线 + APX500 信号生成对比 vs xilink 音频流分析 / QXDM 工具)
  24. 用户 15:02 拍板拆分:Realtime stage button(本 ADR-17)+ XiLink-Analyze stage button(后续 ADR-18)
  25. 共享底座:本 ADR §3.3 log module schema + §3.4 RMS / scope display module schema + DSP 落盘协议

2. 决议(Decision · v0.2 · 范围收窄到 Realtime Stage)

2.1 核心架构图(v0.2 · ASCII · 双 stage button + 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 验)
})

① 输入/输出契约

// 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)
})

3.4 需求块 ④ · Widget endpoint 4 类 + RMS/scope 独立弹窗 module 底座

① 输入/输出契约

// 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 文件正交可并行)。