跳转至
MIGRATED

系统架构设计 (v6.0)

1. 系统概述

本系统是一套车载 DSP 算法调音工具,支持算法链路的动态构建、参数实时调整与下发。系统分三层:Vue3 前端 → .NET8 后端 → C DSP 固件(或 PC 仿真 DLL)。

┌───────────────────────────────────────────────────────────────────────────────────────────┐
│                                前端调音工具 (Frontend)                                      │
│                        Vue3 + TypeScript + Pinia + Vite                                   │
│                                                                                           │
│  ┌────────────────────┐    ┌───────────────────────────────────────┐   ┌───────────────┐  │
│  │   模块调色板        │    │           链路编辑画布                  │   │  调音弹窗      │  │
│  │   (LeftPanel)      │    │  LinkEditor (Canvas + Pan/Zoom)        │   │  (每模块独立)  │  │
│  │                    │    │                                        │   │               │  │
│  │ moduleLibrary.ts:  │    │  ChainNode (模块节点)                  │   │ GainTuning    │  │
│  │   source_v1        │    │  ├── instanceId / moduleType           │   │ DelayTuning   │  │
│  │   channel_gain_v1  │    │  ├── ports[]: PortDescriptor           │   │ XisndTuning   │  │
│  │   ut_delay_20ch_v1 │    │  │    ├── Wire格式信息                 │   │               │  │
│  │   xisnddemom_v1    │    │  │    │  (channels/blockSize/          │   │ BottomProperty│  │
│  │   common_eq_v1     │    │  │    │   sampleRate/dataType)         │   │ Panel         │  │
│  │   ...35+ modules   │    │  │    └── isComplex/rows/cols/IPC      │   └───────────────┘  │
│  └────────────────────┘    │  └── params: ModuleParams              │                      │
│                             │       ├── GainModuleParams            │   ┌───────────────┐  │
│                             │       │    gainDb[] / mute[]          │   │ StatusBar     │  │
│                             │       ├── XisndModuleParams           │   └───────────────┘  │
│                             │       │    vehicleCfg / algoChanNum   │                      │
│                             │       │    memSizeAlgo / tuningParams │                      │
│                             │       └── SourceModuleParams          │                      │
│                             │            blockSize/numChannels/...  │                      │
│                             │  ChainEdge (连线)                     │                      │
│                             │  ├── fromModule/fromPort              │                      │
│                             │  ├── toModule/toPort                  │                      │
│                             │  └── channels/sampleRate/blockSize    │                      │
│                             └───────────────────────────────────────┘                      │
│                                                                                           │
│  linkStore (Pinia):  currentLink(LinkConfig) / params Map / workspace                     │
│  LinkConfig: { version:"3.0", global:{sr,bs,dt}, chains:{ root: ChainDefinition } }      │
└──────────────────────────────────────────┬────────────────────────────────────────────────┘
                                           │ WebSocket (JSON) ws://host:5000/ws
                                           │ write_link / set_param / get_param
┌──────────────────────────────────────────▼────────────────────────────────────────────────┐
│                                  后端服务 (Backend)                                         │
│                             ASP.NET Core 8 (.NET 8)                                       │
│                                                                                           │
│  ┌───────────────┐  ┌──────────────────────────────────────────────────────────────────┐  │
│  │  WS Handler   │  │   ChainFlattener(链路展平服务)                                   │  │
│  │  消息路由      │  │                                                                  │  │
│  │               │  │  LinkConfig (JSON)                                               │  │
│  │ write_link    │  │      │ 1. 递归展平 SubGraphNode → 前缀化 instanceId              │  │
│  │ set_param     │  │      │ 2. PropagateWireInfo() → 所有-1端口解析为确定性值         │  │
│  │ get_param     │  │      │ 3. TypeNameToNumId()                                     │  │
│  │ read_link     │  │      ▼                                                           │  │
│  │ set_ambiance  │  │  FlattenedLinkConfig                                             │  │
│  └───────────────┘  │  ├── modules[]: FlatModuleEntry                                 │  │
│                      │  │    ├── instanceId / typeNumId                                │  │
│  ┌───────────────┐  │  │    ├── ports[]: ResolvedPortSpec                             │  │
│  │  ParamStore   │  │  │    │    (channels/sr/blockSize/dataType 已全部确定)           │  │
│  │  参数缓存      │  │  │    └── initParams[]: ParamSetEntry{typeId,chIdx,value}       │  │
│  └───────────────┘  │  └── connections[]: FlatConnectionEntry                         │  │
│                      │       {fromIdx,fromPortIdx,toIdx,toPortIdx,ch,sr}               │  │
│  ┌───────────────┐  │      │                                                           │  │
│  │ ModuleRegistry│  │      ▼                                                           │  │
│  │ 模块注册表     │  │  BinaryFrameBuilder                                              │  │
│  └───────────────┘  │  ├── BuildSetLink()  → CMD=0x02 Binary Frame v3                 │  │
│                      │  └── BuildSetParam() → CMD=0x01 Binary Frame                   │  │
│  ┌───────────────┐  └──────────────────────────────────────────────────────────────────┘  │
│  │ DspConnection │                                                                         │
│  │ TCP / Serial  │  ┌──────────────────────────────────────────────────────────────────┐  │
│  └───────────────┘  │   AudioEngineService (PC仿真音频引擎)                              │  │
│                      │   ├── NAudio WASAPI 声卡枚举/采集/播放                            │  │
│                      │   └── P/Invoke → DynChain_Process()                              │  │
│                      └──────────────────────────────────────────────────────────────────┘  │
└─────────┬──────────────────────────────────────────────────┬──────────────────────────────┘
          │ Binary Frame v3 (TLV)                             │ P/Invoke (DllImport)
          │ CMD=0x02 set_link                                 │ PC 仿真路径
          │ CMD=0x01 set_param                                │
┌─────────▼──────────────────────────┐         ┌─────────────▼────────────────────────────┐
│       DSP 算法层(嵌入式)           │         │      PC 仿真层                             │
│   C 语言嵌入式 DSP 固件              │         │  DynamicChain.dll + NAudio               │
│                                    │         │                                           │
│  ┌──────────────────────────────┐  │         │  ┌──────────────────────────────────┐    │
│  │    DynamicChain 框架          │  │         │  │   DynamicChain 框架(同源代码)    │    │
│  │                              │  │         │  │                                  │    │
│  │  instances[]:ModuleInstance  │  │         │  │  instances[]:ModuleInstance      │    │
│  │  ┌──────────────────────┐   │  │         │  │  ┌──────────────────────────┐   │    │
│  │  │ gain#1               │   │  │         │  │  │ gain#1                   │   │    │
│  │  │ typeId:channel_gain  │   │  │         │  │  │ pState→GainModuleData    │   │    │
│  │  │ portCfg:{in:20ch,    │   │  │         │  │  │  ├params.gainDb[0..19]   │   │    │
│  │  │          out:20ch}   │   │  │         │  │  │  └channelState[0..19]    │   │    │
│  │  │ pState→GainModuleData│   │  │         │  │  └──────────────────────────┘   │    │
│  │  └──────────────────────┘   │  │         │  │  ┌──────────────────────────┐   │    │
│  │  ┌──────────────────────┐   │  │         │  │  │ xisnd#1                  │   │    │
│  │  │ xisnd#1              │   │  │         │  │  │ pState→awe_modXisnd...   │   │    │
│  │  │ typeId:xisnddemom_v1 │   │  │         │  │  │  ├instance(ModuleInst)   │   │    │
│  │  │ portCfg:{            │   │  │         │  │  │  ├enable/VehicleCfg      │   │    │
│  │  │  in:20ch,fract32,    │   │  │         │  │  │  ├AlgoChanNum/NumOutChan │   │    │
│  │  │  out:20ch,fract32}   │   │  │         │  │  │  ├MemSize_Algo/Arch/Tune │   │    │
│  │  │ pState→              │   │  │         │  │  │  ├TuningBuffer(80K words)│   │    │
│  │  │ awe_modXisndDemom    │   │  │         │  │  │  └AlgoMem(3M words)      │   │    │
│  │  │  Instance            │   │  │         │  │  │    ├adsp_v1_platform      │   │    │
│  │  │  ├AlgoMem(3M words)  │   │  │         │  │  │    ├adsp_audio_vtbl_t    │   │    │
│  │  │  └TuningBuffer(80K)  │   │  │         │  │  │    ├HandlWrapper         │   │    │
│  │  └──────────────────────┘   │  │         │  │  │    ├MediaFmt(Input)      │   │    │
│  │                              │  │         │  │  │    └MediaFmt(Output)     │   │    │
│  │  connections[]:              │  │         │  │  └──────────────────────────┘   │    │
│  │  DynChainConnection          │  │         │  │                                  │    │
│  │  [{from:0,to:1,ch:20,        │  │         │  │  connections[] / orderedIndices[]│    │
│  │    sr:48000},...]             │  │         │  │  MemPool[≥14MB for Xisnd]       │    │
│  │  orderedIndices[]:topo order │  │         │  └──────────────────────────────────┘    │
│  │  MemPool[≥14MB]:             │  │         │                                           │
│  │  GainModuleData(440B)        │  │         │  声卡→DynChain_Process→声卡             │
│  │  +outputBuf(20ch×240×4B)    │  │         │  WAV抓取 / DynChain_GetMetrics           │
│  └──────────────────────────────┘  │         └──────────────────────────────────────────┘
└────────────────────────────────────┘

2. 核心需求

需求 说明
动态链路构建 调音工具可在 UI 上拖拽组合模块,动态生成算法处理链
参数实时下发 调整任意参数后,变更实时通过 WebSocket → Backend → DSP 硬件生效
参数双向同步 DSP 状态可回读到前端;多终端同时连接时参数变更广播同步
链路持久化 链路配置可保存为 JSON 文件,支持断电恢复和版本管理
模块化扩展 新算法模块只需实现标准接口即可接入系统,无需修改框架代码
以 Gain/Delay 为原型 首先以增益(Gain)和延迟(Delay)两个模块验证全链路框架
三种工作模式 系统支持 Chain Builder / Tuning / Test-Verify 三种前端工作模式,各模式对链路编辑和参数操作权限不同
调试窗口 提供延时、算力、内存、WAV 抓取、日志等多个调试面板,供开发和测试使用
PC 仿真 后端调用 DynamicChain.dll + NAudio/PortAudio,无需实体 DSP 硬件即可在 PC 上实时运行算法链路
自动化测试 支持基于 YAML 脚本的自动化测试,可批量验证参数精度,输出 JUnit XML 报告
可变通道/采样率 链路中途通道数或采样率可变(如 Upmixer 2ch→12ch),模块级端口配置独立于全局配置
C# 音频引擎可行性 评估并选定 C# 作为 PC 仿真音频引擎(P/Invoke + NAudio 为原型推荐方案)
多端口扇入 Mixer/Router 等模块可声明 N 个输入端口;ChainCanvas 以具名端口 ID 可视化多路连线扇入
子图嵌套画布 Group 节点封装子链路;双击进入子画布编辑;面包屑导航跨层级跳转
跨层数据结构对齐 前端 JSON (LinkConfig) → 后端展平 (FlattenedLinkConfig) → Binary Frame v3 → DSP DynamicChain 连接表,各层数据结构显式定义并对齐

3. 三种工作模式

系统支持三种前端工作模式,后端根据当前模式在消息处理器中检查操作权限。

┌─────────────────────────────────────────────────────────────────────┐
│                           模式切换示意                               │
│                                                                      │
│  [Chain Builder]  ──── 全功能模式,链路结构完全可编辑               │
│  [Tuning Mode]    ──── 仅加载本地 JSON,参数可调,链路锁定           │
│  [Test/Verify]    ──── 可实时读取链路,参数可读写,链路结构只读      │
└─────────────────────────────────────────────────────────────────────┘

3.1 Mode A — Chain Builder(链路构建模式)

用途:开发阶段,完整编辑算法链路结构并下发到 DSP 或 PC 仿真。

能力 说明
链路编辑 添加/删除模块,调整 order,修改通道数和采样率
参数编辑 所有参数(系统参数 + 调音参数)均可修改
链路回读 可从 DSP 或 PC 仿真引擎读取当前运行链路
保存/加载 从本地文件或后端 JSON 保存/加载链路
DSP/PC 连接 支持连接实体 DSP(TCP/串口)或 PC 仿真引擎

被锁定内容:无(全功能开放)

3.2 Mode B — Tuning Mode(调音模式)

用途:调音工程师在已固定链路基础上调整音效参数,防止误改链路结构。

能力 说明
链路加载 仅从本地 JSON 文件加载,不可实时从 DSP 读取
参数编辑 仅 tunable 参数(增益 dB、延迟 samples、EQ 参数等)可调
链路查看 链路结构只读展示(不可编辑画布)
保存 可保存参数快照(仅参数,链路结构不变)

被锁定内容: - 模块顺序(order) - 通道数(channels) - 采样率(sampleRate) - 帧大小(frameSize) - 模块添加/删除

参数可调性分类:

类别 可调 说明
tunable 是 gainDb, delaySamples, EQ 参数等
system 否 enable(模块使能), smoothTime, 链路顺序

3.3 Mode C — Test/Verify(测试验证模式)

用途:在 Mode B 基础上增强,可在线读取 DSP 实时状态进行验证。

能力 说明
链路获取 可选:从 DSP/PC 实时读取链路结构(只读)
参数读写 所有 tunable 参数可读写
参数回读 可从 DSP 实时回读当前参数值
链路结构 只读,不可修改
调试工具 完整 Debug Panel 可用(见第 9 节)

被锁定内容:链路编辑(与 Mode B 相同)

3.4 后端模式权限适配

后端通过 set_mode 消息切换当前模式,并在相应消息处理器中检查权限:

模式 A (chain_builder): write_link、set_param、connect_dsp 全部允许
模式 B (tuning):        write_link 拒绝;仅 paramRole=tunable 的 set_param 允许
模式 C (test_verify):   write_link 拒绝;set_param 允许;新增 get_dsp_chain 允许

4. 模块分类系统

系统中的算法模块按功能分为以下类别:

类别 ID 名称 说明 示例模块
gain 增益控制 音量、增益、静音 channel_gain_v1, headroom_v1
delay 延迟 通道对齐延迟 ut_delay_20ch_v1
filter 滤波器/EQ 参数均衡、频率整形 common_eq_v1, eq_4ch_v1
dynamics 动态处理 压缩、限幅 limiter_v1, mdrc_v1
mixing 混音/上混 声道混合、环绕声 surround_v1, upmixer_v1
soundDesign 音效设计 失真、虚拟低音 distortion_v1, virtual_bass_v1
spatialAudio 空间音频 混响、声像 ese_aves_v1, rm_reverb_20ch_v1
voice 语音处理 降噪、回声消除 ecnr_v1
logic 逻辑控制 条件路由 logic_v1
debug 调试工具 日志、测试音 log_window_v1, td_chime_v1

5. 部署场景

场景一:本地调试(开发机)

PC(Frontend + Backend) ──TCP──> DSP板卡
  • Frontend: http://localhost:5173
  • Backend: ws://localhost:5000/ws
  • DSP: TCP 192.168.x.x:9000 或串口 COM3

场景二:局域网调试(多端接入)

调音电脑(Backend) <──WiFi──> 手机/平板(Frontend)
调音电脑(Backend) ──Serial──> DSP板卡
  • Backend 绑定 0.0.0.0:5000
  • 多个前端客户端同时连接,参数变更广播同步

场景三:跨网络(公网/VPN)

云服务器(Backend) <──Internet──> 远程Frontend
云服务器(Backend) ──VPN──> 车载DSP

场景四:PC 仿真(无实体 DSP)

PC(Frontend + Backend + DynamicChain.dll + NAudio)
前端 WebSocket ──► 后端(ws://localhost:5000/ws)
后端 P/Invoke ──► DynamicChain.dll (C 算法库)
NAudio WASAPI ──► 声卡采集 ──► DLL Process() ──► 声卡播放
  • 无需连接实体 DSP 硬件
  • 算法链路与嵌入式固件共用同一 C 代码库(DLL 形态)
  • 前端通过相同的 WebSocket 接口(端口 5000)进行调音操作

6. 核心数据流

6.1 参数下发流程

用户在前端调整参数
       │
       ▼
前端 useParamStore.setParam(instanceId, paramId, value)
       │ WebSocket: set_param { instanceId, paramId, value, channel }
       ▼
后端 HandleSetParam()
  ├── paramStore["gain#1.gainDb#0"] = "-3.5"   (内存存储)
  ├── BinaryFrame.Build(instanceId, paramId, value) → byte[]
  │         └── DSP Binary Frame (TLV 格式)
  ├── dspConn.SendAsync(frame)                  (异步发送)
  └── BroadcastExcept(sender, param_update)     (多端同步)
       │ Binary Frame
       ▼
DSP DynamicChain_SetParam(instanceId, paramId, rawValue)
  └── 找到对应 ModuleInstance → Module.SetParam()

6.2 链路构建流程

用户在链路编辑画布拖拽模块
       │
       ▼
前端 useChainStore.addModule(moduleType, position)
  └── 生成 ChainNode { instanceId, moduleType, ports, position }
       │
       │ (保存时) WebSocket: write_link { chains, global, rootChainId }
       ▼
后端 HandleWriteLink()
  ├── 持久化 JSON → ./data/current_link.json
  ├── ChainFlattener.Flatten(rootChainId) → FlattenedLinkConfig
  └── 解析展平链路 → 生成 DSP 链路配置二进制 (Binary Frame v3)
       │ Binary Frame v3 CMD=0x02 (set_link)
       ▼
DSP DynamicChain_LoadConfig(flattenedConfig)
  ├── 按连接表注册模块实例: ModuleRegistry.Create(moduleType)
  ├── 分配每模块独立输出缓冲区: PerModuleOutputBuffer.Alloc()
  └── 初始化每个模块: module.Init()

6.3 参数回读流程

前端重连或手动同步
       │ WebSocket: get_all_params { instanceId }
       ▼
后端 HandleGetAllParams()
  └── 从 paramStore 返回该实例所有参数
       │ get_all_params_ack { params: {...} }
       ▼
前端更新 Pinia Store → UI 同步刷新

6.4 PC 仿真音频数据流

声卡输入 (NAudio WASAPI)
       │ float[] 音频帧 (nbSamples × inputChannels)
       ▼
AudioEngineService.OnAudioInput()
       │ P/Invoke
       ▼
DynChain_Process(pChain, ppIn, ppOut, nbSamples)   [DynamicChain.dll]
  ├── 按连接表顺序调用每个 ModuleInstance.Process()
  └── 每模块使用独立输出缓冲区 (PerModuleOutputBuffer)
       │ float[] 处理后音频帧
       ▼
声卡输出 (NAudio WASAPI)

6.5 调试数据采集流程

DSP/算法层 各模块 Process() 执行
  ├── 记录 Process() 耗时 → cpuUs[]
  ├── 上报 MemPool.used / total
  └── 注入延时量 → latencyMs[]
       │ (周期上报,默认 100ms 间隔)
       ▼
后端 DebugDataService.Collect()
  ├── 汇总各模块 CPU 占用
  ├── 汇总内存使用
  └── 计算总链路延时
       │ WebSocket 推送: debug_metrics { latency, compute, memory }
       ▼
前端 DebugPanel 实时刷新显示
  ├── DebugLatencyPanel  → 总延时及各模块延时分解
  ├── DebugComputePanel  → 各模块 CPU 占用排行
  └── DebugMemoryPanel   → 内存池使用率

7. 链路端口配置(多端口 / 子图 / 跨层对齐)

7.1 问题背景

旧版 DSPLink 的平铺端口字段(inputChannels / outputChannels / inputSampleRate / outputSampleRate)存在两个不足:

  1. 单端口假设:每个模块只能有一个输入端口和一个输出端口,无法支持 Mixer(多路扇入)、Router(多路扇出)等模块。
  2. 无子图概念:复杂链路只能平铺展示,无法将一组模块封装为可复用的 Group 节点。

v3.0 引入 PortDescriptor、SubGraphNode 和 LinkConfig(替换旧 DSPLink)来解决上述问题。

7.2 核心类型定义(TypeScript)

// -------------------------------------------------------
// 端口描述符(v6.0:完整 Wire 信息,对应 AWE WireInstance 位域)
// -------------------------------------------------------
interface PortDescriptor {
  id:        string                   // 端口唯一标识,如 "input_0"、"output"
  direction: 'input' | 'output'
  required:  boolean                  // false 表示可选端口(用户可删除)
  label:     string                   // 面板显示名称,如 "Ch A"
  // wireInfo1 字段
  channels:  number                   // -1 表示继承上游;正整数表示固定值(对应 wireInfo1[9:0])
  maxBlockSize?: number               // 最大 block 大小(wireInfo1[26:10])
  isComplex?: boolean                 // 是否复数信号(wireInfo1[27])
  sampleSizeBytes?: number            // 每采样字节数(wireInfo1[31:28])
  // wireInfo2 字段
  blockSize:  number                  // 当前 block 大小,-1 继承(wireInfo2[16:0])
  dataType:   WireDataType | -1       // 数据类型,-1 继承(wireInfo2[22:17])
  // wireInfo3 字段
  rows?: number                       // 矩阵行数(wireInfo3[9:0])
  cols?: number                       // 矩阵列数(wireInfo3[19:10])
  isIPC?: boolean                     // 跨核 IPC(wireInfo3[20])
  isPrivate?: boolean                 // 私有 wire(wireInfo3[21])
  isClockMaster?: boolean             // 时钟主源(wireInfo3[22])
  // Wire 基本属性
  sampleRate: number                  // -1 表示继承上游(WireInstance.sampleRate)
}

type WireDataType = 'float32' | 'fract32' | 'int16' | 'int32' | 'int24'

// -------------------------------------------------------
// 模块算法参数基类(v6.0:与 PortDescriptor Wire信息严格分离)
// -------------------------------------------------------
interface ModuleParams {
  [key: string]: unknown
}

// 具体模块参数类型(示例)
interface GainModuleParams extends ModuleParams {
  enable: number
  gainDb: number[]          // 每通道增益(dB)
  mute: number[]            // 每通道静音
  phase: number[]           // 每通道极性翻转
  smoothTimeMs?: number
}

interface XisndModuleParams extends ModuleParams {
  enable: number
  vehicleCfg: number        // 车型配置
  algoChanNum: number       // 算法处理通道数
  numOutChan: number        // 输出通道数
  memSizeAlgo: number       // 算法核心内存(words)
  memSizeArch: number       // 架构内存(words)
  memSizeTune: number       // 调音缓冲内存(words)
  tuningParams?: TuningParam[] // 在线调音参数(通过TuningBuffer通道传递)
}

interface SourceModuleParams extends ModuleParams {
  blockSize: number
  numChannels: number
  sampleRate: number
  dataType: WireDataType
  isComplex: number
}

// -------------------------------------------------------
// 普通链路节点(v6.0:ports = Wire信息,params = 算法参数,两者严格分离)
// -------------------------------------------------------
interface ChainNode {
  instanceId: string
  moduleType: string
  order:      number
  enabled:    boolean
  position:   { x: number; y: number }
  ports:      PortDescriptor[]        // ① Wire 格式信息(随 set_link 下发)
  params?:    ModuleParams            // ② 算法参数(随 set_param 独立下发)
}

// -------------------------------------------------------
// 子图节点:moduleType 固定为 'subgraph'
// -------------------------------------------------------
interface SubGraphNode {
  instanceId: string
  moduleType: 'subgraph'
  subGraphId: string                  // 引用 LinkConfig.chains 中的子链路 ID
  position:   { x: number; y: number }
  ports:      PortDescriptor[]        // 镜像子链路的 externalPorts
}

// -------------------------------------------------------
// 连线:fromPort / toPort 替换旧的隐式单端口假设
// -------------------------------------------------------
interface ChainEdge {
  id:         string
  fromModule: string                  // 上游节点 instanceId
  fromPort:   string                  // 上游端口 ID(对应 PortDescriptor.id)
  toModule:   string                  // 下游节点 instanceId
  toPort:     string                  // 下游端口 ID
  channels:   number                  // 连线上的实际信号通道数(前端自动计算)
  sampleRate: number                  // 连线上的实际采样率(前端自动计算)
}

// -------------------------------------------------------
// 链路定义:普通链路 or 子图链路
// -------------------------------------------------------
interface ChainDefinition {
  id:            string
  name:          string
  nodes:         (ChainNode | SubGraphNode)[]
  edges:         ChainEdge[]
  externalPorts?: PortDescriptor[]    // 仅子图链路需要,定义对外暴露的端口
}

// -------------------------------------------------------
// 顶层配置(v3.0:含 dataType 全局字段,替换旧 v2.2 格式)
// -------------------------------------------------------
interface LinkConfig {
  version:     '3.0'
  rootChainId: string                 // 根链路 ID,通常为 "root"
  global: {
    channels:   number
    sampleRate: number
    blockSize:  number
    dataType:   WireDataType          // v3.0 新增:全局默认数据类型
  }
  chains: Record<string, ChainDefinition>  // key = chainId
}

7.3 ModuleDescriptor 端口规则(v3.0)

interface ModuleDescriptor {
  moduleType:   string
  category:     string
  displayName:  string
  // defaultPorts 替换旧的 portRule 字段
  defaultPorts: PortDescriptor[]
  // 可选:Mixer 等模块支持用户动态增减输入端口
  maxDynamicInputPorts?: number
  params:       ParamDescriptor[]
}
  • defaultPorts 直接列出模块所有默认端口,不再区分 fixed / passthrough / configurable 类型,规格由 channels: -1(继承)或正整数(固定)表达。
  • maxDynamicInputPorts 非零时,前端画布允许用户在该模块上增加或删除输入端口(如 Mixer 的 input_0 … input_N)。

示例(Mixer 模块):

const AudioMixerDescriptor: ModuleDescriptor = {
  moduleType:  'audio_mixer_v1',
  category:    'mixing',
  displayName: 'Audio Mixer',
  defaultPorts: [
    { id: 'input_0', direction: 'input',  channels: -1, sampleRate: -1, label: 'Ch A', required: true  },
    { id: 'input_1', direction: 'input',  channels: -1, sampleRate: -1, label: 'Ch B', required: false },
    { id: 'output',  direction: 'output', channels: -1, sampleRate: -1, label: 'Mix Out', required: true },
  ],
  maxDynamicInputPorts: 8,
  params: [],
}

7.4 链路信号流传播规则

前端 ChainCanvas 在链路编辑时,沿 ChainEdge 传播信号规格:

  1. 根链路起始节点:取 LinkConfig.global.channels 和 LinkConfig.global.sampleRate。
  2. 对每条 ChainEdge:
  3. 上游节点 fromPort 的 channels/sampleRate 若为 -1,向上游递归解析实际值。
  4. 解析后的值写入 ChainEdge.channels 和 ChainEdge.sampleRate(前端自动计算,不由用户手动填写)。
  5. 若相邻端口规格不匹配(如通道数不符),画布在对应连线上显示橙色警告。

7.5 子图展平策略(后端 ChainFlattener)

ChainFlattener 在后端将含子图的 LinkConfig 展平为 DSP 可直接消费的 FlattenedLinkConfig:

展平规则:
  1. 遍历根链路 nodes:
     - 普通 ChainNode → 直接保留,instanceId 不变
     - SubGraphNode (instanceId="group#1") → 递归展平子链路
         子链路中每个节点的 instanceId 前缀化:
         "group#1.upmixer#1"、"group#1.delay#1" ...
  2. 边的处理:
     - 子图内部边:fromModule/toModule 同步前缀化
     - 跨子图边(父→子图入口 / 子图出口→父):
         替换 SubGraphNode 端点为子图内对应的边界模块
  3. 展平结果 FlattenedLinkConfig 只包含普通模块和连接表,
     不含任何 SubGraphNode 或 subGraphId 引用
  4. DSP 收到的是完全平坦的模块列表 + 连接表,
     对子图结构完全透明

展平后 instanceId 示例:

父链路中 "group#1" 子图包含:
  upmixer#1, delay#1

展平后 instanceId:
  group#1.upmixer#1
  group#1.delay#1

7.6 DSP 端适配(v3.0)

DSP 不再使用全局 ChainRunConfig + 单一 ping-pong 缓冲区,改为:

  1. 连接表(Connection Table):显式记录 fromModuleIdx / fromPortIdx / toModuleIdx / toPortIdx,DSP 按连接表驱动数据路由,不依赖模块顺序。
  2. 每模块独立输出缓冲区:每个 ModuleInstance 拥有自己的 PerModuleOutputBuffer,取代单一 ping-pong 缓冲区。
/* 连接表项(由 set_link Binary Frame v3 附带传递)*/
typedef struct ConnectionEntry_ {
    uint16_t fromModuleIdx;
    uint16_t fromPortIdx;
    uint16_t toModuleIdx;
    uint16_t toPortIdx;
} ConnectionEntry;

/* 每模块的端口配置(同样由 set_link 帧附带传递)*/
typedef struct ModulePortCfg_ {
    uint32_t inputChannels;
    uint32_t outputChannels;
    uint32_t inputSampleRate;
    uint32_t outputSampleRate;
} ModulePortCfg;

8. PC 仿真架构

8.1 PC 仿真运行原理

┌─────────────────────────────────────────────────────────────────────┐
│                         PC 仿真数据流                                │
│                                                                      │
│  [音频输入] ──►  AudioEngineService  ──►  [音频输出]                 │
│  (声卡/文件)      │                          (声卡/文件)              │
│                   │ P/Invoke                                         │
│                   ▼                                                  │
│          DynamicChain.dll (C 算法库)                                 │
│          ├── ChannelGain_v1                                          │
│          ├── UtDelay_20ch_v1                                         │
│          └── ...                                                     │
│                                                                      │
│  前端 WebSocket ──► 后端 ──► DynChain_SetParam()                     │
│                     (同实体 DSP 接口,固定端口 5000)                  │
└─────────────────────────────────────────────────────────────────────┘

8.2 技术方案分析(C# + C 算法库)

方案一(推荐原型):C# P/Invoke + NAudio

  • C 算法库编译为 DynamicChain.dll(Windows)/ libDynamicChain.so(Linux)
  • C# 通过 [DllImport] P/Invoke 调用算法接口
  • NAudio WASAPI Exclusive 模式采集/播放(10–20 ms 延迟)
  • 音频回调中禁用 GC 压缩:GCSettings.LatencyMode = SustainedLowLatency
  • 优点:单进程,开发最简单,沿用现有 .NET8 架构
  • 缺点:C# GC 偶发停顿(< 1ms),WASAPI 需 Windows,延迟略高于纯 C

方案二(推荐生产):双进程(C# Web + C 音频进程)

  • C# ASP.NET Core 8 负责 WebSocket/REST
  • 单独 C 进程(PortAudio + DynamicChain)负责音频 I/O
  • 两进程间通过本地 TCP/共享内存通信参数变更
  • 优点:C 进程无 GC,音频延迟最低(< 5 ms);GC 不干扰音频线程
  • 缺点:进程间通信增加复杂度

方案三:前端 WASM

  • C 算法库通过 Emscripten 编译为 WebAssembly
  • 在浏览器 Web Audio API 中运行,AudioWorklet 调用 WASM
  • 优点:跨平台(Windows/Mac/Linux/移动端)
  • 缺点:性能约为原生 3–5 倍开销;Web Audio 固定 128 样本帧(2.67ms @ 48kHz);与系统声卡隔离,延迟较高

方案四:纯 C + 嵌入 HTTP/WebSocket

  • PortAudio + DynamicChain + libwebsockets 全部用 C 实现
  • 优点:最高效,延迟最低
  • 缺点:需完全重写后端,放弃 .NET 生态,开发成本高

结论:

场景 推荐方案
原型验证 / 快速开发 方案一(P/Invoke + NAudio)
生产级 / 低延迟需求 方案二(双进程 PortAudio)
跨平台演示 / 无声卡 方案三(WASM)

8.3 DLL 导出接口

算法库需暴露以下 C 接口供 P/Invoke 调用:

// DynamicChain.dll / libDynamicChain.so 导出接口
EXPORT int32_t  DynChain_Create(DynamicChain **ppChain);
EXPORT int32_t  DynChain_Destroy(DynamicChain *pChain);
EXPORT int32_t  DynChain_LoadConfig(DynamicChain *pChain,
                                    const char *configJson, uint32_t len);
EXPORT int32_t  DynChain_SetParam(DynamicChain *pChain,
                                   const char *instanceId,
                                   const char *paramId,
                                   const void *pValue, uint32_t size);
EXPORT int32_t  DynChain_Process(DynamicChain *pChain,
                                  float **ppIn, float **ppOut,
                                  uint32_t nbSamples);

9. 调试子系统

9.1 调试功能分类

功能 前端 后端 DSP/算法层
系统延时显示 DebugLatencyPanel 计算总延时 每模块延时注入量
算力分析 DebugComputePanel 汇总各模块 CPU 占用 Process() 时间戳
内存分析 DebugMemoryPanel 汇总 MemPool 使用 MemPool.used / total
WAV 节点抓取 DebugWavPanel 触发抓取,返回 WAV 文件 Process() 后 hook
Log 显示 DebugLogPanel 转发日志流 LOG_CALLBACK 函数
参数回读 复用 ParamPanel 回读 DSP/算法实时值 GetParam()
单位转换工具 ConversionToolPanel — —

9.2 数据采集接口(后端推送)

后端通过 WebSocket 周期推送调试数据:

{
  "type": "debug_metrics",
  "timestamp": 1711900000000,
  "latency": { "totalMs": 12.5 },
  "compute": [
    { "instanceId": "gain#1", "cpuUs": 45 },
    { "instanceId": "delay#1", "cpuUs": 120 }
  ],
  "memory": { "poolUsed": 204800, "poolTotal": 1048576 }
}

WAV 抓取结果通过 REST 接口 GET /api/debug/wav/{captureId} 以二进制流返回。


10. 自动化测试框架

10.1 测试脚本格式(YAML)

# test_gain_precision.yaml
name: "Gain 精度测试 - 20通道"
target: tcp://192.168.1.100:9000   # 目标 DSP 或 pc_sim
chain: ./data/test_chain.json      # 测试链路
steps:
  - action: set_param
    instanceId: channel_gain#1
    paramId: gainDb#0
    value: -6.0
  - action: set_param
    instanceId: channel_gain#1
    paramId: gainDb#1
    value: -12.0
  - action: wait_ms
    ms: 100
  - action: capture_wav
    node: channel_gain#1
    duration_ms: 1000
    output: ./results/gain_ch0_ch1.wav
  - action: verify_rms
    file: ./results/gain_ch0_ch1.wav
    channel: 0
    expected_rms_db: -6.0
    tolerance_db: 0.5
  - action: verify_rms
    file: ./results/gain_ch0_ch1.wav
    channel: 1
    expected_rms_db: -12.0
    tolerance_db: 0.5

10.2 测试运行方式

# 命令行运行单个测试
dotnet Backend.dll --test test_gain_precision.yaml

# 批量运行(返回 JUnit XML 报告)
dotnet Backend.dll --test-suite ./tests/ --report ./results/report.xml

11. 模块实例标识

系统中每个运行时模块实例使用如下 ID 方案:

instanceId = "${moduleType}#${index}"

例:
  gain#1        → 第 1 个增益模块实例
  delay#1       → 第 1 个延迟模块实例
  eq#2          → 第 2 个均衡器模块实例

子图展平后,子图内部模块的 instanceId 前缀化:

"${groupInstanceId}.${childInstanceId}"

例:
  group#1.upmixer#1   → group#1 子图内的第 1 个 upmixer 实例
  group#1.delay#1     → group#1 子图内的第 1 个 delay 实例

参数键格式(paramId 含通道后缀):

"${instanceId}.${paramId}"

例:
  gain#1.enable           → 增益模块 enable 标志
  gain#1.gainDb#0         → 增益模块通道 0 的增益值
  delay#1.delaySamples#3  → 延迟模块通道 3 的延迟量

12. 链路配置 JSON 格式(v3.0)

系统以 JSON 描述算法链路,保存于 ./data/current_link.json。v3.0 格式新增 global.dataType 字段,并将 global.frameSize 重命名为 global.blockSize,完整对应 AWE Wire 信息:

{
  "version": "3.0",
  "rootChainId": "root",
  "global": { "channels": 20, "sampleRate": 48000, "blockSize": 240, "dataType": "fract32" },
  "chains": {
    "root": {
      "id": "root", "name": "Main Chain",
      "nodes": [
        { "instanceId": "gain#1", "moduleType": "channel_gain_v1", "order": 0, "enabled": true,
          "position": {"x":100,"y":200},
          "ports": [
            {"id":"input","direction":"input","channels":-1,"sampleRate":-1,"label":"Input","required":true},
            {"id":"output","direction":"output","channels":-1,"sampleRate":-1,"label":"Output","required":true}
          ]
        },
        { "instanceId": "group#1", "moduleType": "subgraph", "subGraphId": "surround_proc",
          "position": {"x":300,"y":200},
          "ports": [
            {"id":"input","direction":"input","channels":2,"sampleRate":48000,"label":"Stereo In","required":true},
            {"id":"output","direction":"output","channels":8,"sampleRate":48000,"label":"7.1 Out","required":true}
          ]
        },
        { "instanceId": "mixer#1", "moduleType": "audio_mixer_v1", "order": 2, "enabled": true,
          "position": {"x":550,"y":200},
          "ports": [
            {"id":"input_0","direction":"input","channels":-1,"sampleRate":-1,"label":"Ch A","required":true},
            {"id":"input_1","direction":"input","channels":-1,"sampleRate":-1,"label":"Ch B","required":false},
            {"id":"output","direction":"output","channels":-1,"sampleRate":-1,"label":"Mix Out","required":true}
          ]
        }
      ],
      "edges": [
        {"id":"e1","fromModule":"gain#1","fromPort":"output","toModule":"mixer#1","toPort":"input_0","channels":2,"sampleRate":48000},
        {"id":"e2","fromModule":"group#1","fromPort":"output","toModule":"mixer#1","toPort":"input_1","channels":8,"sampleRate":48000}
      ]
    },
    "surround_proc": {
      "id": "surround_proc", "name": "Surround Processor",
      "externalPorts": [
        {"id":"input","direction":"input","channels":2,"sampleRate":48000,"label":"Stereo In","required":true},
        {"id":"output","direction":"output","channels":8,"sampleRate":48000,"label":"7.1 Out","required":true}
      ],
      "nodes": [
        { "instanceId": "upmixer#1", "moduleType": "upmixer_2to8_v1", "order": 0, "enabled": true,
          "position": {"x":100,"y":100},
          "ports": [
            {"id":"input","direction":"input","channels":2,"sampleRate":48000,"label":"Stereo","required":true},
            {"id":"output","direction":"output","channels":8,"sampleRate":48000,"label":"7.1","required":true}
          ]
        }
      ],
      "edges": []
    }
  }
}

字段说明: - version:格式版本号,当前为 "3.0"(v6.0 架构) - global.dataType:v3.0 新增,全局默认 Wire 数据类型(float32/fract32/int16 等) - global.blockSize:v3.0 重命名自 frameSize,对应 AWE wireInfo2[16:0] - rootChainId:根链路 ID,对应 chains 中的一个键,DSP 从此链路开始执行 - chains:所有链路定义的映射表,key 为 chainId;根链路和所有子图链路均存放于此 - nodes[].ports:端口描述符数组,替换旧版平铺的 inputChannels / outputChannels 等字段 - nodes[].moduleType = "subgraph":标识该节点为子图节点,subGraphId 指向 chains 中的子链路 - edges[].fromPort / edges[].toPort:端口 ID,替换旧版隐式单端口假设 - edges[].channels / edges[].sampleRate:连线上的实际信号规格,由前端自动计算并写入 - externalPorts:仅子图链路需要,定义该子图对外暴露的端口,与父链路中 SubGraphNode.ports 保持镜像


13. 技术栈汇总

层 技术 版本 说明
前端 Vue3 3.x 响应式 UI 框架
前端 TypeScript 5.x 类型安全
前端 Pinia 2.x 状态管理
前端 Vite 5.x 构建工具
后端 .NET 8.0 运行时
后端 ASP.NET Core 8.0 Web + WebSocket
后端 System.Text.Json 内置 JSON 序列化
后端音频 NAudio 2.x WASAPI 音频 I/O(PC 仿真方案一)
后端音频 PortAudio 19.x 跨平台音频 I/O(PC 仿真方案二 C 进程)
后端 P/Invoke DllImport .NET 内置 调用 C 算法库 DLL
DSP/PC仿真 C (C99) — 嵌入式 DSP 固件 + PC DLL(同源代码库)
DSP CAPI v3 接口 — DSP 平台适配层
通信 WebSocket RFC 6455 前后端全双工
通信 TLV 二进制帧 自定义 v3 后端↔DSP

14. 关键设计原则

  1. 模块接口统一:每个算法模块必须实现 5 个标准接口(GetMemSize / Init / Process / SetParam / GetParam)
  2. 运行时动态加载:链路结构在运行时通过注册表动态组装,不依赖编译期枚举顺序
  3. 参数单位前后端一致:前端 UI 单位(dB, ms, cm)→ 后端转换 → DSP 内部单位(linear, samples)
  4. 无损热更新参数:单个参数更新不中断音频流(平滑过渡)
  5. 链路重配置需静音过渡:增删模块等结构性变更需 mute→变更→unmute 保护
  6. 状态可恢复:断线重连后前端可通过 get_all_params + read_link 完整恢复状态
  7. 三种模式权限隔离:后端按当前工作模式(Chain Builder / Tuning / Test-Verify)检查每条消息的操作权限,拒绝越权操作并返回明确错误
  8. 算法库双形态:同一 C 代码库同时支持嵌入式静态库(DSP 固件)和 PC 动态库(DLL/SO),通过条件编译区分平台适配层,算法核心代码不做修改
  9. 可观测性优先:算法库内置日志回调(LOG_CALLBACK)、每模块 Process() 性能计时和 WAV 抓取 Hook,调试子系统可在不修改算法代码的前提下获取完整运行时信息
  10. 端口显式化:所有模块连接通过具名端口 ID 建立,不依赖隐式顺序;ChainEdge 的 fromPort/toPort 字段与 PortDescriptor.id 一一对应,确保多端口扇入/扇出语义无歧义
  11. 子图透明性:前端子图对 DSP 完全透明,后端 ChainFlattener 负责将所有 SubGraphNode 递归展平为平坦模块列表和连接表;DSP 只看到平坦的模块-连接表,不感知子图层级结构

  12. Wire 信息与算法参数分离(v6.0):PortDescriptor(Wire 格式)与 ModuleParams(算法参数)在前端/后端/DSP 三层均保持独立存储和独立传输,互不干扰。Wire 参数随 set_link 下发,算法参数随 set_param 单独下发。

  13. Xisnd 两阶段初始化(v6.0):Xisnd 模块需先通过 set_link 配置 Wire 规格(portCfg),再通过 set_param(SETALLMASK) 触发全量初始化(platform_init),后才能接受音频数据处理。顺序不可颠倒。

  14. 在线调音通道(TuningBuffer)(v6.0):Xisnd 的参数调整分两类:① Build 参数(vehicleCfg 等)通过 set_param 直接写入 AWE 结构体;② 调音参数通过 TuningBuffer 专用通道(MASK=0x8000)传递,以 COMMAND_HEADER_WRITE/READ 区分读写方向。

  15. 内存池扩展策略(v6.0):含 Xisnd 的链路内存需求约 12MB,嵌入式平台需使用外部 DRAM 池,或将 AlgoMem/TuningBuffer 单独申请于 DRAM 段而非统一 MemPool。当前 DYNAMIC_CHAIN_POOL_SIZE 需从 1MB 扩展至 ≥14MB。


15. Wire 信息三层对应关系(v6.0)

参考:debug/awe_module_structure_analysis.md 中 WireInstance 位域定义

前端 PortDescriptor              后端 ResolvedPortSpec         DSP WireInstance / PortSpec
───────────────────              ─────────────────────         ───────────────────────────
channels:     20         ──►     Channels:    20       ──►     wireInfo1[9:0]  = 20
maxBlockSize: 240        ──►     (传入portSpec)         ──►     wireInfo1[26:10] = 240
isComplex:    false      ──►     IsComplex:   0         ──►     wireInfo1[27]   = 0
sampleSizeBytes: 4       ──►     SampleSizeB: 4         ──►     wireInfo1[31:28] = 4
blockSize:    240        ──►     BlockSize:   240       ──►     wireInfo2[16:0]  = 240
dataType:     'fract32'  ──►     DataType:    3(enum)   ──►     wireInfo2[22:17] = 3
rows:         0          ──►     Rows:        0         ──►     wireInfo3[9:0]  = 0
cols:         0          ──►     Cols:        0         ──►     wireInfo3[19:10] = 0
isIPC:        false      ──►     IsIPC:       0         ──►     wireInfo3[20]   = 0
isPrivate:    false      ──►     IsPrivate:   0         ──►     wireInfo3[21]   = 0
isClockMaster: false     ──►     IsClockMaster: 0       ──►     wireInfo3[22]   = 0
sampleRate:   48000      ──►     SampleRate:  48000     ──►     WireInstance.sampleRate (float)

16. Xisnd 模块参数三层对应关系(v6.0)

参考:debug/awe_module_structure_analysis.md 中 awe_modXisndDemomInstance 字段

前端 XisndModuleParams           后端 ParamSetEntry[]           DSP awe_modXisndDemomInstance
──────────────────────           ───────────────────            ─────────────────────────────
enable:      1          ──►     {typeId:0x0103, ch:0, 1.0}  ──►  S->enable = 1
                                                              ──►  packedFlags = MODULE_ACTIVE
vehicleCfg:  0          ──►     {typeId:0x0200, ch:0, 0.0}  ──►  S->VehicleCfg = 0
                                                              ──►  awe_modXisndDemomSet(0x200)
                                                              ──►  → _pif->vehicleCfg = 0
algoChanNum: 20         ──►     {typeId:0x0201, ch:0, 20.0} ──►  S->AlgoChanNum = 20
                                                              ──►  awe_modXisndDemomSet(0x400)
numOutChan:  20         ──►     {typeId:0x0202, ch:0, 20.0} ──►  S->NumOutChan = 20
memSizeAlgo: 3000116    ──►     {typeId:0x0203, ch:0, ...}  ──►  S->MemSize_Algo = 3000116
memSizeArch: 0          ──►     {typeId:0x0204, ch:0, ...}  ──►  S->MemSize_Arch = 0
memSizeTune: 80000      ──►     {typeId:0x0205, ch:0, ...}  ──►  S->MemSize_Tune = 80000

[初始化触发]
initFull: true          ──►     CMD=0x01 mask=0xFFFFFFFF     ──►  awe_modXisndDemomSet(SETALLMASK)
                                                              ──►  → AlgoMem 内部布局初始化
                                                              ──►  → platform_init(_pif) 调用

[在线调音]
tuningParams[0]:        ──►     TuningBuffer 内容构建:        ──►  S->TuningBuffer[0] = 0x44332211
  paramId: 0x16670004             TuningBuffer[0]=0x44332211           (WRITE命令头)
  data: [...]                     TuningBuffer[1]=0x16670004   ──►  awe_modXisndDemomSet(0x8000)
                                  TuningBuffer[2]=dataLen       ──►  → _pif->set_param(0x16670004)
                                  TuningBuffer[3..]=data[]

Tunable 参数分类(v6.0):

模块 Tunable(调音可调) System(锁定)
Gain gainDb[], mute[], phase[] enable, smoothTimeMs
Delay delaySamples[] enable
Xisnd tuningParams[](在线调音,通过TuningBuffer) enable, vehicleCfg, algoChanNum, numOutChan, memSize*
Source — 全部(Wire规格属于Build配置)

17. 内存需求估算(v6.0)

组件 内存需求 说明
GainModuleData (20ch) ~440 B params + channelState[]
GainModuleData outputBuf 20×240×4 = 19.2 KB 每模块独立输出缓冲
DelayModuleData (20ch) ~77 KB 20通道循环缓冲区
awe_modXisndDemomInstance (结构体) ~72 B 9个字段 + 基类32B
Xisnd TuningBuffer 80,000×4 = 312 KB 在线调音缓冲区
Xisnd AlgoMem 3,000,116×4 ≈ 11.4 MB 算法全量内存
Xisnd outputBuf 20×240×4 = 19.2 KB 每模块独立输出缓冲
单链路总计(gain+delay+xisnd) ≈ 12 MB 主要来自 Xisnd AlgoMem

⚠️ 嵌入式注意:当前 DYNAMIC_CHAIN_POOL_SIZE = 1MB,需扩展至 ≥14MB 以容纳 Xisnd 模块。 PC 仿真环境无限制,可使用系统 malloc 代替 MemPool。


新节 A:子图链路回读设计

问题:DSP 硬件回读时返回平坦模块列表,子图层次结构丢失。

解决方案:后端持有层次结构的唯一权威来源。

操作流程对比:

get_dsp_chain(前端读链路):
  Frontend → Backend → 读取 current_link.json → 返回完整 LinkConfig(含子图)
  ✅ 子图层次结构完整保留

read_dsp_chain(同步校验):
  Frontend → Backend → CMD=0x06 → DSP → 回传平坦模块列表
  Backend ChainSyncService 对比存储配置 vs DSP 回读
  → 不一致时广播 chain_sync_warning

原则:链路层次结构(LinkConfig with sub-graphs)永远由 Backend current_link.json 持有,DSP 回读仅用于运行态校验,不用于重建层次结构。


新节 B:混合 ID 策略

系统采用 字符串(JSON 层)+ 数字(Binary 层)混合 方案:

层 ID 类型 原因
Frontend ↔ Backend WebSocket JSON 字符串 可读性、schema 一致、工具友好
Backend ↔ DSP Binary Frame 数字 紧凑、DSP O(1) 查找、带宽节省 75%
DSP 调试 Log 字符串(可选宏) 调试时可读

set_param 帧格式对比

版本 格式 大小
旧版(字符串) [CMD][LEN]["gain#1.gainDb#2\0"][f32] ~30 字节
新版 v3(数字) [CMD=0x01][LEN=9][moduleIdx:u8][paramTypeId:u16][channelIdx:u16][type:u8][f32] 14 字节

paramTypeId 编号规范(u16)

高字节 = 模块类别:
  0x01 = gain    0x02 = filter/EQ    0x03 = delay
  0x04 = dynamics  0x05 = mixer     0x06 = routing
低字节 = 该类别内参数编号(从 0x01 起)

示例:
  0x0101 = Gain.gainDb    0x0102 = Gain.mute
  0x0201 = EQ.bandFreq    0x0202 = EQ.bandGain   0x0203 = EQ.bandQ
  0x0301 = Delay.delaySamples

新节 C:多维参数寻址

EQ 等模块有多通道多波段参数(10ch × 10band × 5类型 = 500组合)。

方案:ParamSchema.dimensions?: ParamDimension[] 替代 channels: number

interface ParamDimension {
  name:    string    // "channel", "band"
  count:   number
  labels?: string[]
}
// EQ bandFreq schema:
{ paramId:"bandFreq", paramTypeId:0x0201,
  dimensions:[{name:"channel",count:10},{name:"band",count:10}],
  min:20, max:20000, unit:"Hz", role:"tunable" }

channelIdx 编码规则: - 标量参数(全局):channelIdx = 0 - 1D(通道):channelIdx = ch_index - 2D(通道×波段):channelIdx = (ch_index << 8) | band_index

模块 旧方案条目数 新方案条目数
Gain(20通道) 20 条 1 条 + dimensions:[{count:20}]
EQ(10ch×10band×5类型) 500 条 5 条 + 2D dimensions

新节 D:音效模式管理架构(三级结构)

ModulePreset(单模块参数快照)
  └── presetId, name, instanceId, params: { "bandFreq[2][5]": "120.0" }
        ↓ N个 ModulePreset 组合成
AmbianceProfile(一种音效模式,如"HIFI"、"动感")
  └── profileId, name, modulePresets: { instanceId → presetId }
        ↓ M个 AmbianceProfile 打包成
ParamBin(二进制导出包,CMD=0x04 下发 DSP)
  └── 全部 ambiance × module × preset 的参数数据,DSP 一次性加载

ParamBin 生成与使用流程

前端 → generate_param_bin → 后端 ParamBinGenerator
     → 二进制 ParamBin → CMD=0x04 → DSP DynChain_LoadParamBin()

音效切换:
前端 → set_ambiance(idx) → CMD=0x05 → DSP DynChain_SetAmbiance(idx)
DSP 内部:O(num_params) 遍历,无字符串操作,直接写入模块参数

ParamBin 二进制格式

[magic:"PCFG":4][version:u8][numAmbiances:u8][numModules:u8][reserved:u8]
Module Table (numModules × 24 bytes): instanceId[16] + typeNumId:u32 + numPresets:u8 + pad:u8[3]
Ambiance Table (numAmbiances × (16+numModules) bytes): name[16] + presetIndices:u8[numModules]
Param Data: per module per preset: [paramCount:u16][paramTypeId:u16 + channelIdx:u16 + value:f32]×N

前端 UI 功能

界面 位置 功能
Preset 面板 每个模块面板右侧 当前模块的参数快照 CRUD
Profile Manager 右侧可折叠侧边栏 全局音效模式管理,快速切换
Param Gen 视图 TopBar 入口 Ambiance × Module 矩阵,生成 ParamBin

新增节 E:AWE 框架验证阶段(v6.0 更新)

验证策略

当前阶段 DSP 算法验证依赖 AudioWeaver (AWE) 框架。原则:在 audiochain 层做 AWE 适配,不修改 DynChain 核心逻辑,验证通过后切换自研实现。

┌────────────────────────────────────────────────────────┐
│          DynChain 框架(不变)                           │
│   ModuleRegistry / DynamicChain / ModuleInstance       │
│   DynChain_ParseLinkFrame / Process / SetParamNum      │
├────────────────────────────────────────────────────────┤
│          AWE 适配层(v6.0:xisnddemom_module.c)         │
│                                                        │
│  XisndDemom_GetMemSize(portCfg)                        │
│    ← sizeof(awe_modXisndDemomInstance)                 │
│    ← + 80000×4 (TuningBuffer)                         │
│    ← + portCfg->memSizeAlgo×4 (AlgoMem)               │
│                                                        │
│  XisndDemom_Init(pState, portCfg)                      │
│    ← portCfg 中提取 channels/sampleRate               │
│    ← 分配 TuningBuffer (80K words)                    │
│    ← 分配 AlgoMem (3M words)                          │
│    ← S->AlgoChanNum = portCfg->ports[0].channels      │
│                                                        │
│  XisndDemom_Process(pState, inputs, ppOut)             │
│    ← inputs[0].ppData = 上游模块.ppOut                 │
│    ← 转换为 sport_buf_t + sport_stream_data_t          │
│    ← 调用 awe_modXisndDemomProcess(pInstance)          │
│                                                        │
│  XisndDemom_SetParamNum(pState, typeId, chIdx, val)    │
│    ← typeId=0x0200 → MASK=0x200 → VehicleCfg          │
│    ← typeId=0x0201 → MASK=0x400 → AlgoChanNum         │
│    ← typeId=0x0202 → MASK=0x800 → NumOutChan          │
│    ← typeId=0x0203 → MASK=0x1000 → MemSize_Algo       │
│    ← typeId=0x0204 → MASK=0x2000 → MemSize_Arch       │
│    ← typeId=0x0205 → MASK=0x4000 → MemSize_Tune       │
│    ← typeId=0xFFFF → SETALLMASK → 全量初始化           │
│    ← typeId=MASK_TuningBuffer → 在线调音读写            │
│                                                        │
│  awe_gainmodule.c / awe_delaymodule.c(其他模块)       │
├────────────────────────────────────────────────────────┤
│          AWE SDK (AWECore lib + platform_api.h)        │
│   platform_init / adsp_audio_vtbl_t.process            │
│   adsp_audio_vtbl_t.set_param/get_param                │
└────────────────────────────────────────────────────────┘

阶段切换计划

阶段 audiochain 实现 关键标志
Phase 1(当前) AWE SDK 调用(xisnddemom_module.c + AWECore) .awd 文件驱动,快速验证算法链路
Phase 2(待切换) 自研 C 实现 替换 awe_*module.c → gain_module.c 等

AWE 模块接口约定(v6.0 更新)

// 每个 AWE 适配模块必须实现 ModuleFuncTable 中的接口:
int32_t XisndDemom_GetMemSize(const ModulePortConfig *portCfg);

int32_t XisndDemom_Init(void *pState, const ModulePortConfig *portCfg);

int32_t XisndDemom_SetParamNum(void *pState, uint16_t paramTypeId,
                               uint16_t channelIdx, const void *pVal, uint32_t size);
//   paramTypeId=0x0200 → MASK_XisndDemom_VehicleCfg  (awe mask 0x200)
//   paramTypeId=0x0201 → MASK_XisndDemom_AlgoChanNum (awe mask 0x400)
//   paramTypeId=0xFFFF → SETALLMASK (触发 platform_init)
//   paramTypeId=0x8000 → MASK_XisndDemom_TuningBuffer (在线调音)

int32_t XisndDemom_GetParamNum(void *pState, uint16_t paramTypeId,
                               uint16_t channelIdx, void *pBuf, uint32_t size);

void    XisndDemom_Process(void *pState, const ModuleInputList *inputs,
                           float **ppOut, uint32_t numOut, uint32_t blockSize);

PC 仿真音频引擎(AudioEngine)

Frontend (Vue3)
  AudioEnginePanel.vue
    ├── 声卡设备选择 (list_audio_devices → audio_device_list)
    ├── 引擎启停控制 (start/stop_audio_engine)
    ├── 挂载模块列表 (来自 DynChain 当前链路)
    └── 实时资源监控 (audio_engine_status 推送,2s 间隔)

Backend (.NET8)
  AudioEngineService.cs
    ├── NAudio WASAPI 设备枚举
    ├── 音频引擎启停
    └── CPU/内存采样推送

PC 仿真 DLL
  dynchain_sim.dll
    └── 链路加载 → AWE audiochain 层(gain/delay/xisnddemom) → AWECore

验证用例:20ch Gain → Xisnd(主验证链路)

[Source 20ch] → [Gain 20ch] → [Xisnd 20ch]
                    ↕ set_param          ↕ set_param
              gainDb[0..19]          vehicleCfg / algoChanNum
                                     + TuningBuffer 在线调音

验证步骤: 1. 前端 LinkEditor 拖拽构建上述链路并 write_link(version="3.0") 2. 后端 ChainFlattener 展平 → PropagateWireInfo() 解析所有 -1 端口 → TypeNameToNumId() → BinaryFrameBuilder.BuildSetLink() → CMD=0x02 3. DSP/DLL DynamicChain_ParseLinkFrame() → 注册 gain + xisnd 模块 → 分配 MemPool(≥14MB) 4. 后端发送参数:set_param(xisnd#1, vehicleCfg=0) + set_param(xisnd#1, algoChanNum=20) + set_param(xisnd#1, SETALLMASK) → platform_init() 成功 5. 前端打开 XisndTuningDialog → 通过 TuningBuffer 通道发送调音参数(paramId=0x16670004) 6. AWE 音频引擎实时处理并通过所选声卡播放