跳转至

系统架构设计 (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 引入 PortDescriptorSubGraphNodeLinkConfig(替换旧 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.channelsLinkConfig.global.sampleRate
  2. 对每条 ChainEdge
  3. 上游节点 fromPortchannels/sampleRate 若为 -1,向上游递归解析实际值。
  4. 解析后的值写入 ChainEdge.channelsChainEdge.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 建立,不依赖隐式顺序;ChainEdgefromPort/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.mdawe_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.cgain_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 音频引擎实时处理并通过所选声卡播放