跳转至

通信协议设计 (v4.0)


1. 概述

本协议描述车载音频 DSP 调音工具的三层通信体系:

  1. 前端 ↔ 后端:Vue3 前端与 ASP.NET Core 8 后端之间的 WebSocket JSON 协议,以及 REST 补充接口
  2. 后端 ↔ DSP:后端与 DSP 硬件(或 PC 仿真)之间的 TLV 二进制帧协议
  3. 参数 ID 命名规范:贯穿前后端和 DSP 的参数寻址格式
┌──────────────────────────────────────────┐
│               Frontend (Vue3 + Pinia)    │
│  ┌────────────┐ ┌────────┐ ┌──────────┐  │
│  │Chain Builder│ │ Tuning │ │Test/Verify│ │
│  └────────────┘ └────────┘ └──────────┘  │
└────────────────┬─────────────────────────┘
                 │ JSON / WebSocket (ws://0.0.0.0:5000/ws)
                 │ HTTP REST (/api/*)
┌────────────────▼─────────────────────────┐
│               Backend (ASP.NET Core 8)   │
│  ┌──────────┐ ┌──────┐ ┌─────────────┐   │
│  │WebSocket │ │ REST │ │AudioEngine  │   │
│  │ Handler  │ │ API  │ │(NAudio+DLL) │   │
│  └──────────┘ └──────┘ └─────────────┘   │
│  ┌────────────────────────────────────┐   │
│  │        DebugService                │   │
│  │  (metrics / log_stream / WAV)      │   │
│  └────────────────────────────────────┘   │
└──────┬───────────────────────┬────────────┘
       │ Binary / TCP or Serial │ DLL call (in-process)
┌──────▼──────┐         ┌──────▼──────────────┐
│ DSP Hardware│         │  PC Sim             │
│  (Embedded) │         │  DynamicChain.dll   │
└─────────────┘         └─────────────────────┘

连接信息

项目
协议 WebSocket
地址 ws://0.0.0.0:5000/ws
文本编码 UTF-8
最大帧大小 65536 bytes
心跳周期 5 秒(前端发送 ping,后端回 pong
重连策略 断线后 3s 自动重试,指数退避最大 30s

消息格式规范

每条 WebSocket 消息必须包含 type 字段:

{ "type": "<消息类型>", ...其他字段 }

2. 前端 → 后端 消息(完整列表)

2.1 基础通信

ping — 心跳探测

{ "type": "ping" }

响应pong


2.2 参数操作

set_param — 设置单个参数

{
  "type": "set_param",
  "instanceId": "gain#1",
  "paramId": "gainDb",
  "value": -3.5,
  "channel": 0
}
字段 类型 必填 说明
instanceId string 模块实例 ID(格式:moduleType#index
paramId string 参数名(不含通道后缀)
value number / bool / string 参数值(UI 单位)
channel int 可选 通道索引(存在时 key 追加 #channel

后端存储 key${instanceId}.${paramId}${instanceId}.${paramId}#${channel}

响应set_param_ack

副作用: 1. 触发 DSP 二进制帧发送(异步,不阻塞响应) 2. 广播 param_update 给其他所有已连接客户端


set_params — 兼容别名

set_param 相同格式,后端视为等价处理(向后兼容)。


get_param — 查询单个参数

{
  "type": "get_param",
  "instanceId": "gain#1",
  "paramId": "gainDb#0"
}

响应get_param_ack


get_all_params — 查询实例全部参数

{
  "type": "get_all_params",
  "instanceId": "gain#1"
}

用于前端重连后恢复该模块的所有参数状态。

响应get_all_params_ack


2.3 链路管理

从 v3.0 起,write_link 消息的 payload 字段从旧版扁平 DSPLink 格式升级为 LinkConfig v2.2 格式,支持多端口 mixer 节点与子图(SubGraph)嵌套。完整示例如下:

{
  "type": "write_link",
  "payload": {
    "version": "2.2",
    "rootChainId": "root",
    "global": { "channels": 20, "sampleRate": 48000, "frameSize": 240 },
    "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": "mixer#1",
            "moduleType": "audio_mixer_v1",
            "order": 1,
            "enabled": true,
            "position": {"x": 400, "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","required":true}
            ]
          },
          {
            "instanceId": "group#1",
            "moduleType": "subgraph",
            "subGraphId": "eq_stage",
            "position": {"x": 700, "y": 200},
            "ports": [
              {"id":"input","direction":"input","channels":2,"sampleRate":48000,"label":"In","required":true},
              {"id":"output","direction":"output","channels":2,"sampleRate":48000,"label":"Out","required":true}
            ]
          }
        ],
        "edges": [
          {"id":"e1","fromModule":"gain#1","fromPort":"output","toModule":"mixer#1","toPort":"input_0","channels":2,"sampleRate":48000},
          {"id":"e2","fromModule":"mixer#1","fromPort":"output","toModule":"group#1","toPort":"input","channels":2,"sampleRate":48000}
        ]
      },
      "eq_stage": {
        "id": "eq_stage",
        "name": "EQ Stage",
        "externalPorts": [
          {"id":"input","direction":"input","channels":2,"sampleRate":48000,"label":"In","required":true},
          {"id":"output","direction":"output","channels":2,"sampleRate":48000,"label":"Out","required":true}
        ],
        "nodes": [
          {
            "instanceId": "eq#1",
            "moduleType": "common_eq_v1",
            "order": 0,
            "enabled": true,
            "position": {"x": 200, "y": 150},
            "ports": [
              {"id":"input","direction":"input","channels":-1,"sampleRate":-1,"label":"In","required":true},
              {"id":"output","direction":"output","channels":-1,"sampleRate":-1,"label":"Out","required":true}
            ]
          }
        ],
        "edges": []
      }
    }
  }
}

后端处理: 1. 将 payload 序列化为 JSON,写入 ./data/current_link.json 2. 调用 ChainFlattener 展平所有子图,构造 set_link v3 二进制帧下发到 DSP 3. 批量提取各链路节点参数,更新 paramStore 4. 下发各模块初始参数(set_param 帧序列)

响应write_link_ack


ChainDefinition 字段说明

字段 类型 必填 说明
id string 链路唯一标识符,与 chains 对象的 key 相同
name string 可读名称,仅供 UI 显示
nodes ChainNode[] 本链路内的所有节点列表
edges ChainEdge[] 本链路内的所有连线列表
externalPorts PortDescriptor[] 仅子图链路需要,定义子图对外暴露的端口

ChainNode 字段说明

字段 类型 必填 说明
instanceId string 模块实例 ID,同一链路内唯一
moduleType string 模块类型字符串;子图节点固定为 "subgraph"
subGraphId string 子图节点必填 指向 chains 中对应子图的 id
order int 处理顺序(升序),子图节点可省略
enabled bool 模块是否启用,默认 true
position {x, y} 画布坐标,仅供 UI 使用
ports PortDescriptor[] 本节点的端口描述列表

PortDescriptor 字段说明

字段 类型 说明
id string 端口 ID,在同一节点内唯一(如 "input_0""output"
direction string "input""output"
channels int 端口通道数;-1 表示继承全局/上游通道数
sampleRate int 端口采样率(Hz);-1 表示继承
label string UI 显示标签
required bool 是否为必连端口;false 表示可选输入

SubGraphNode 补充说明

SubGraphNodeChainNode 的子类型,当 moduleType == "subgraph" 时生效:

字段 类型 说明
subGraphId string 引用 LinkConfig.chains 中的子图 id
ports PortDescriptor[] 与被引用子图的 externalPorts 一一对应,通道/采样率已解析

后端 ChainFlattener 会将子图节点递归展开为平坦模块列表,子图内部节点的 instanceId 在展平后采用点分路径格式(如 "group#1.eq#1")。

ChainEdge 字段说明

字段 类型 必填 说明
id string 边的唯一 ID(同一链路内唯一)
fromModule string 源节点 instanceId
fromPort string 源节点端口 ID
toModule string 目标节点 instanceId
toPort string 目标节点端口 ID
channels int 该连线上已解析的通道数
sampleRate int 该连线上已解析的采样率(Hz)

{ "type": "read_link" }

./data/current_link.json 加载链路 JSON。

响应read_link_ack


2.4 DSP 连接

connect_dsp — 连接 DSP 硬件

TCP 模式

{
  "type": "connect_dsp",
  "protocol": "tcp",
  "host": "192.168.1.100",
  "port": 9000
}

串口模式

{
  "type": "connect_dsp",
  "protocol": "serial",
  "comPort": "COM3",
  "baudRate": 115200
}

响应connect_dsp_ack

副作用:连接成功后广播 dsp_status 给所有客户端


disconnect_dsp — 断开 DSP 连接

{ "type": "disconnect_dsp" }

响应disconnect_dsp_ack

副作用:广播 dsp_status 给所有客户端


get_status — 查询后端及 DSP 状态

{ "type": "get_status" }

响应dsp_status


get_dsp_chain — 从 DSP 读取链路结构(仅 test_verify 模式)

从 DSP 实时读取当前运行的链路结构(只读):

{ "type": "get_dsp_chain" }

响应get_dsp_chain_ack


2.5 工作模式

set_mode — 切换工作模式

切换工作模式,广播给所有连接客户端:

{
  "type": "set_mode",
  "mode": "tuning"
}
mode 取值 说明
chain_builder 链路构建模式(全功能,可增删模块、修改连接)
tuning 调音模式(链路锁定,仅 tunable 参数可调)
test_verify 测试验证模式(链路只读,可读写 tunable 参数,可读取 DSP 链路)

响应mode_changed(广播给所有客户端)


2.6 PC 仿真

sim_start — 启动 PC 端算法仿真

后端调用 DynamicChain.dll + NAudio 启动音频仿真处理:

{
  "type": "sim_start",
  "input": {
    "type": "soundcard",
    "deviceName": "立体声混音 (Realtek HD Audio)"
  },
  "output": {
    "type": "soundcard",
    "deviceName": "扬声器 (Realtek HD Audio)"
  }
}

文件输入模式:

{
  "type": "sim_start",
  "input": { "type": "file", "filePath": "./test_audio/white_noise_2ch.wav" },
  "output": { "type": "soundcard", "deviceName": "扬声器" }
}

响应sim_status(广播给所有客户端)


sim_stop — 停止 PC 端算法仿真

{ "type": "sim_stop" }

响应sim_status(广播给所有客户端)


list_audio_devices — 枚举系统音频设备

{ "type": "list_audio_devices" }

响应audio_devices_ack


2.7 调试

debug_subscribe — 订阅调试指标周期推送

{
  "type": "debug_subscribe",
  "intervalMs": 500
}

后端开始按 intervalMs 间隔周期推送 debug_metrics


debug_unsubscribe — 取消订阅调试指标

{ "type": "debug_unsubscribe" }

wav_capture_start — 在指定模块节点启动 WAV 音频抓取

{
  "type": "wav_capture_start",
  "instanceId": "ut_delay_20ch#1",
  "capturePoint": "output",
  "durationMs": 1000
}
字段 说明
instanceId 目标模块实例 ID
capturePoint "input""output"
durationMs 抓取时长(毫秒)

响应wav_capture_ack(立即返回 captureId),抓取完成后推送 wav_capture_done


3. 后端 → 前端 消息(完整列表)

3.1 基础响应

pong — 心跳响应

{
  "type": "pong",
  "timestamp": 1711900000000
}

timestamp 为 UTC 毫秒时间戳,前端用于计算 RTT。


error — 错误响应

{
  "type": "error",
  "message": "未知指令类型: unknown_cmd"
}

3.2 参数响应

set_param_ack — 参数设置确认

成功时:

{
  "type": "set_param_ack",
  "instanceId": "gain#1",
  "paramId": "gainDb#0",
  "success": true
}

失败时:

{
  "type": "set_param_ack",
  "instanceId": "gain#1",
  "paramId": "gainDb#0",
  "success": false,
  "error": "DSP 未连接"
}


get_param_ack — 参数查询响应

{
  "type": "get_param_ack",
  "instanceId": "gain#1",
  "paramId": "gainDb#0",
  "value": "-3.5",
  "success": true
}

注:value 字段为字符串(paramStore 以字符串存储所有值)。


get_all_params_ack — 全量参数响应

{
  "type": "get_all_params_ack",
  "instanceId": "gain#1",
  "params": {
    "enable": "true",
    "smoothTime": "10",
    "gainDb#0": "-3.0",
    "gainDb#1": "-3.0",
    "mute#0": "false",
    "mute#1": "false",
    "phase#0": "false"
  }
}

键为去掉 ${instanceId}. 前缀后的 paramId,值均为字符串。


param_update — 多端参数广播

某客户端设置参数后,后端广播给其余所有已连接客户端:

{
  "type": "param_update",
  "instanceId": "gain#1",
  "paramId": "gainDb#0",
  "value": "-3.5"
}

3.3 链路响应

成功时:

{
  "type": "write_link_ack",
  "success": true
}

失败时:

{
  "type": "write_link_ack",
  "success": false,
  "error": "链路格式无效"
}


成功时:

{
  "type": "read_link_ack",
  "success": true,
  "link": { "version": "2.2", "rootChainId": "root", "chains": {...} }
}

文件不存在时:

{
  "type": "read_link_ack",
  "success": false,
  "link": null
}


get_dsp_chain_ack — DSP 链路读取响应

{
  "type": "get_dsp_chain_ack",
  "success": true,
  "link": { "version": "2.2", "rootChainId": "root", "chains": {...} }
}

3.4 DSP 状态

connect_dsp_ack — DSP 连接结果

成功时:

{
  "type": "connect_dsp_ack",
  "success": true,
  "protocol": "tcp",
  "target": "192.168.1.100:9000",
  "error": null
}

失败时:

{
  "type": "connect_dsp_ack",
  "success": false,
  "protocol": "tcp",
  "target": "",
  "error": "连接超时"
}


disconnect_dsp_ack — DSP 断开确认

{
  "type": "disconnect_dsp_ack",
  "success": true
}

dsp_status — DSP 状态推送

客户端连接时主动推送一次,DSP 连接/断开时全体广播:

{
  "type": "dsp_status",
  "connected": true,
  "protocol": "tcp",
  "target": "192.168.1.100:9000"
}

3.5 模式

mode_changed — 模式变更广播

{
  "type": "mode_changed",
  "mode": "tuning"
}

3.6 PC 仿真

sim_status — 仿真状态推送

仿真状态变更时广播给所有客户端:

正常运行时:

{
  "type": "sim_status",
  "running": true,
  "status": "running",
  "inputDevice": "立体声混音 (Realtek HD Audio)",
  "outputDevice": "扬声器 (Realtek HD Audio)"
}

失败时:

{
  "type": "sim_status",
  "running": false,
  "status": "error",
  "error": "WASAPI 独占模式被其他程序占用"
}


audio_devices_ack — 音频设备列表

{
  "type": "audio_devices_ack",
  "inputs": [
    { "id": "dev-001", "name": "立体声混音 (Realtek HD Audio)" },
    { "id": "dev-002", "name": "麦克风阵列 (USB Audio)" }
  ],
  "outputs": [
    { "id": "dev-010", "name": "扬声器 (Realtek HD Audio)" },
    { "id": "dev-011", "name": "耳机 (USB Audio)" }
  ]
}

3.7 调试推送

debug_metrics — 调试指标周期推送

订阅后按 intervalMs 间隔推送:

{
  "type": "debug_metrics",
  "timestamp": 1711900000000,
  "latency": {
    "totalMs": 22.5,
    "byModule": [
      { "instanceId": "channel_gain#1",  "latencyMs": 0.0 },
      { "instanceId": "ut_delay_20ch#1", "latencyMs": 20.0 }
    ]
  },
  "compute": [
    { "instanceId": "channel_gain#1",  "cpuUs": 45 },
    { "instanceId": "ut_delay_20ch#1", "cpuUs": 95 }
  ],
  "memory": {
    "poolUsed": 204800,
    "poolTotal": 1048576,
    "byModule": [
      { "instanceId": "channel_gain#1",  "bytes": 440 },
      { "instanceId": "ut_delay_20ch#1", "bytes": 77056 }
    ]
  }
}

log_stream — 实时日志流推送

算法库或后端产生的日志实时推送:

{
  "type": "log_stream",
  "level": "INFO",
  "source": "dsp",
  "message": "DynamicChain: LoadConfig done, 2 modules",
  "timestamp": 1711900000000
}
level 说明
DEBUG 低优先级诊断信息(高频,默认不显示)
INFO 正常运行信息
WARN 警告(如 MemPool 占用超过 80%)
ERROR 错误事件
source 说明
dsp 来自 DynamicChain 算法库
backend 来自 ASP.NET Core 后端

wav_capture_ack — WAV 抓取启动确认

{
  "type": "wav_capture_ack",
  "captureId": "a3f2b1c0"
}

wav_capture_done — WAV 抓取完成通知

{
  "type": "wav_capture_done",
  "captureId": "a3f2b1c0",
  "fileSizeBytes": 2097152
}

前端收到后通过 REST 接口下载:GET /api/debug/wav/a3f2b1c0


4. REST 接口

方法 路径 说明
GET /api/status 查询后端及 DSP 状态(JSON)
GET /api/link 获取当前链路快照(JSON)
GET /api/debug/wav/{captureId} 下载 WAV 抓取文件(二进制流)
GET /api/debug/metrics 获取最近一次调试指标快照(JSON)
GET /api/audio/devices 获取系统音频设备列表(JSON)

/api/status 响应示例:

{
  "status": "online",
  "clients": 2,
  "version": "3.0",
  "dsp": {
    "connected": true,
    "protocol": "tcp",
    "target": "192.168.1.100:9000"
  },
  "paramCount": 48
}


5. DSP 二进制帧协议(后端 ↔ DSP)

后端与 DSP 硬件之间使用 TLV 风格二进制帧。

5.1 通用帧结构

Offset  Size  字段
──────  ────  ──────────────────────────────────────
0       1     帧头 0x55
1       1     帧头 0xAA
2       1     命令字 CMD
3       2     序列号 SEQ (uint16 LE, 每帧自增,溢出归零)
5       2     数据长度 LEN (uint16 LE,仅 DATA 段字节数)
7       N     数据段 DATA(由 CMD 决定格式)
7+N     1     校验 CRC8(计算范围:偏移 0 ~ 7+N-1)

总帧长 = 8 + N bytes


5.2 命令字定义

CMD 方向 说明
set_param 0x01 后端→DSP 设置单个模块参数
set_link 0x02 后端→DSP 下发完整链路配置
ping 0x03 后端→DSP 心跳探测(保留)
param_ack 0x81 DSP→后端 参数设置确认(保留)
link_ack 0x82 DSP→后端 链路加载确认(保留)

5.3 CMD = 0x01:set_param DATA 格式

Offset    Size  字段
──────    ────  ──────────────────────────────────────
0         1     instanceId 字节长度 (N1)
1         N1    instanceId (UTF-8,如 "gain#1")
1+N1      1     paramId 字节长度 (N2)
2+N1      N2    paramId (UTF-8,已含通道后缀,如 "gainDb#0")
2+N1+N2   1     值类型 VALUE_TYPE
3+N1+N2   V     值字节 VALUE(长度由 VALUE_TYPE 决定)

值类型编码

VALUE_TYPE 字节数 说明
float32 0x01 4 IEEE 754 单精度,LE
int32 0x02 4 有符号整数,LE
uint8 0x03 1 布尔值(1=true, 0=false)或 uint8 整数
uint32 0x04 4 无符号整数,LE(用于延迟 samples 等)
string 0x05 N UTF-8 字符串(无长度前缀,由 LEN 推导)

后端类型推断规则SerializeValue() 函数): 1. 如果 paramId 包含关键字 "Samples""Idx"uint32 (0x04) 2. "true" / "false"uint8 (0x03) 3. 否则尝试 float.TryParse → float32 (0x01) 4. 否则尝试 int.TryParse → int32 (0x02) 5. 其余 → string (0x05)


v3 采用固定宽度的模块表和连线表,后端 ChainFlattener 负责将 LinkConfig v2.2 中的子图递归展平后再构造此帧。DSP 仅见到平坦的模块列表,对子图结构无感知。

set_link v3 binary frame (CMD=0x02):

Header (6 bytes):
  [CMD: uint8 = 0x02]
  [DATA_LEN: uint32 LE]   -- bytes following CMD byte
  [VERSION: uint8 = 0x03]
  [NUM_MODULES: uint8]
  [NUM_CONNECTIONS: uint8]
  [RESERVED: uint8]

Module Table (NUM_MODULES × 88 bytes per entry):
  Per module entry (88 bytes total):
    instanceId:  char[16]      null-terminated, e.g. "gain#1\0..."
    typeNumId:   uint32 LE     e.g. 0x10010001
    numPorts:    uint8         number of valid entries in ports[8]
    RESERVED:    uint8[3]
    ports[8]:                  8 × 8 bytes = 64 bytes
      Per port (8 bytes):
        portId:    char[6]     null-terminated, e.g. "input\0", "out\0.."
        direction: uint8       0=input, 1=output
        required:  uint8       0=optional, 1=required
        channels:  uint16 LE   0=inherit
        sampleRate: uint16 LE  sample rate / 1000 (e.g. 48 for 48000 Hz) — saves space

  Note: 16 + 4 + 4 + 64 = 88 bytes per module
  Maximum 32 modules → 32 × 88 = 2816 bytes

Connection Table (NUM_CONNECTIONS × 8 bytes per entry):
  Per connection entry (8 bytes):
    fromModuleIdx: uint8   index into module table
    fromPortIdx:   uint8   index into fromModule's ports[] array
    toModuleIdx:   uint8
    toPortIdx:     uint8
    channels:      uint16 LE   resolved channel count on this wire
    sampleRate:    uint16 LE   sample rate / 1000

  Maximum 64 connections → 64 × 8 = 512 bytes

Total maximum frame payload: 6 + 2816 + 512 = 3334 bytes

设计说明: - 连线表使用模块索引(而非字符串 ID)引用模块,节省帧空间并加快 DSP 端解析速度 - 子图由后端 ChainFlattener 展平;DSP 仅见到平坦模块列表,不感知子图层次结构 - 模块表中的 instanceId 字符串采用点分展平命名(如 "group#1.eq#1"),用于参数寻址(例如 "group#1.eq#1.gainDb#0"),与前端展平后的路径保持一致

DSP 端解析后的行为: 1. 停止音频处理(静音保护) 2. 调用 DynamicChain_Reset() 清空旧链路 3. 按顺序 DynamicChain_AddModule() 重建链路 4. 恢复音频处理


向后兼容说明:v1 与 v2 格式(旧固件)

以下 v1/v2 格式仅供尚未升级至 v3 的旧固件参考。后端可根据 targetVersion 配置决定使用哪种格式。

v1 格式(原始版本)

Offset  Size   字段
──────  ─────  ──────────────────────────────────────
0       2      模块数量 (uint16 LE)
2       ...    模块条目列表(按 order 升序排列)

每个模块条目(v1):
  0     4      moduleTypeId (uint32 LE)
  4     1      instanceId 字节长度 (N)
  5     N      instanceId (UTF-8,如 "gain#1")

v2 格式(含端口配置字段)

Offset  Size   字段
──────  ─────  ──────────────────────────────────────
0       2      模块数量 (uint16 LE)
2       ...    模块条目列表(按 order 升序排列)

每个模块条目(v2):
  0     4      moduleTypeId (uint32 LE)
  4     1      instanceId 字节长度 (N)
  5     N      instanceId (UTF-8,如 "gain#1")
  5+N   2      inputChannels  (uint16 LE)
  7+N   2      outputChannels (uint16 LE)
  9+N   4      inputSampleRate  (uint32 LE)
  13+N  4      outputSampleRate (uint32 LE)

DSP 可根据帧 LEN 字段推断版本(v2 每个模块条目比 v1 多 12 字节)。


模块类型 ID 定义

moduleType 字符串 moduleTypeId 说明
channel_gain_v1 0x10010001 20 通道增益
adaptive_gain_v1 0x10010002 自适应增益
speaker_level_v1 0x10010003 扬声器电平
headroom_v1 0x10010004 Headroom 增益
audio_mixer_v1 0x10010005 多输入混音器
ut_delay_20ch_v1 0x10020001 20 通道延迟
common_eq_v1 0x10030001 通用参数 EQ
eq_4ch_v1 0x10030002 4 通道 EQ
source_eq_v1 0x10030003 音源 EQ
speed_eq_v1 0x10030004 车速 EQ
ut_eq_20ch_v1 0x10030005 20 通道 EQ
limiter_v1 0x10040001 限幅器
mdrc_v1 0x10040002 多频段压缩
surround_v1 0x10050001 环绕声
upmixer_v1 0x10050002 上混(如 2ch→12ch)
distortion_v1 0x10060001 失真效果
virtual_bass_v1 0x10060002 虚拟低音
loudness_v1 0x10060003 响度补偿

5.5 CRC8 算法

多项式 0x07(CRC-8/SMBUS),计算范围:偏移 0 到 DATA 末尾(不含 CRC 字节本身):

byte Crc8(byte[] data, int start, int len) {
    byte crc = 0;
    for (int i = start; i < start + len; i++) {
        crc ^= data[i];
        for (int j = 0; j < 8; j++)
            crc = (crc & 0x80) != 0 ? (byte)((crc << 1) ^ 0x07) : (byte)(crc << 1);
    }
    return crc;
}

5.6 完整帧示例

示例 1:set_param — 设置 gain#1gainDb#0 = -3.5(float32)

55 AA                         帧头
01                            CMD = set_param
00 00                         SEQ = 0
13 00                         LEN = 19 bytes

DATA (19 bytes):
  06                          instanceId len = 6
  67 61 69 6E 23 31           "gain#1"
  08                          paramId len = 8
  67 61 69 6E 44 62 23 30     "gainDb#0"
  01                          VALUE_TYPE = float32
  00 00 60 C0                 -3.5 (IEEE 754 LE)

XX                            CRC8

示例 2:set_param — 设置 delay#1delaySamples#1 = 48(uint32)

55 AA                         帧头
01                            CMD = set_param
01 00                         SEQ = 1
12 00                         LEN = 18 bytes

DATA (18 bytes):
  07                          instanceId len = 7
  64 65 6C 61 79 23 31        "delay#1"
  10                          paramId len = 16
  64 65 6C 61 79 53 61 6D
  70 6C 65 73 23 31           "delaySamples#1"
  04                          VALUE_TYPE = uint32
  30 00 00 00                 48 (LE)

XX                            CRC8
55 AA                         帧头
02                            CMD = set_link
02 00                         SEQ = 2
16 00                         LEN = 22 bytes

DATA (22 bytes):
  02 00                       模块数量 = 2

  条目 0 (gain#1):
    01 00 01 10               moduleTypeId = 0x10010001 (channel_gain_v1, LE)
    06                        instanceId len = 6
    67 61 69 6E 23 31         "gain#1"

  条目 1 (delay#1):
    01 00 02 10               moduleTypeId = 0x10020001 (ut_delay_20ch_v1, LE)
    07                        instanceId len = 7
    64 65 6C 61 79 23 31      "delay#1"

XX                            CRC8

6. 参数 ID 命名规范

格式定义

格式 示例 说明
paramId enable 全局参数(无通道后缀)
paramId#channel gainDb#0 逐通道参数(channel 从 0 起)
paramId#band bandFreq#2 逐频段参数
paramId#band#channel bandFreq#2#0 逐频段逐通道

WebSocket 消息中的参数 ID

前端发送 set_param 时,paramId 字段不含通道后缀,通道通过独立的 channel 字段传递:

{ "type": "set_param", "instanceId": "gain#1", "paramId": "gainDb", "value": -3.0, "channel": 0 }

后端合并为存储 key:"gain#1.gainDb#0"

DSP 二进制帧中的参数 ID

DSP 帧中的 paramId 字段已包含通道后缀,直接传递完整字符串:

"gainDb#0"          → gain#1 模块通道 0 的增益
"delaySamples#3"    → delay#1 模块通道 3 的延迟
"enable"            → 模块使能(无通道后缀)

instanceId 格式规范

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

规则:
  1. moduleTypeShortName 为 moduleType 去掉 "_v${N}" 后缀后的名称
     channel_gain_v1  → channel_gain
     ut_delay_20ch_v1 → ut_delay_20ch
  2. index 从 1 开始,同类型第 N 个实例的 index 为 N
  3. 总长度不超过 31 字节(ASCII)
  4. 子图展平后采用点分路径,如 "group#1.eq#1"(总长度不超过 15 字节,
     受 set_link v3 模块表 instanceId char[16] 限制)

示例:
  channel_gain#1        第 1 个 channel_gain_v1 实例
  ut_delay_20ch#1       第 1 个 ut_delay_20ch_v1 实例
  channel_gain#2        第 2 个 channel_gain_v1 实例
  group#1.eq#1          子图 group#1 内的 eq#1(展平后)

Gain 模块参数 ID 列表

paramId 类型 单位 通道相关 说明
enable bool 模块使能
smoothTime float ms 增益平滑时间
gainDb#${ch} float dB 通道 ch 增益(-96 ~ +24 dB)
mute#${ch} bool 通道 ch 静音
phase#${ch} bool 通道 ch 相位翻转

Delay 模块参数 ID 列表

paramId 类型 单位 通道相关 说明
enable bool 模块使能
delaySamples#${ch} uint32 samples 通道 ch 延迟(0 ~ 960 samples)

7. 链路配置 JSON 格式(v2.2)

v2.2 在 v2.1 基础上,将单链路平坦模块列表升级为多链路 chains 映射,支持子图嵌套(SubGraph)与多端口连线(multi-port edge)。旧版 v2.0/v2.1 的单一 input/output 连线假设已被 ChainEdge.fromPort / toPort 取代,可明确指向具名端口 ID。

注意:ChainEdge.fromPort / toPort 替代旧版单一 input/output 假设,明确指向具名端口 ID。这使得 Mixer 等多输入模块的不同输入端口(如 input_0input_1)可以在同一连线表中被唯一区分。

LinkConfig 字段说明

字段 类型 必填 说明
version string 格式版本,当前为 "2.2"
rootChainId string 根链路 id,对应 chains 中的顶层入口
global object 全局音频参数:channelssampleRateframeSize
chains map 所有链路定义,key 为链路 id

ChainDefinition 字段说明

字段 类型 必填 说明
id string 链路唯一标识符
name string 可读名称
nodes ChainNode[] 本链路内所有节点
edges ChainEdge[] 本链路内所有连线
externalPorts PortDescriptor[] 子图必填 子图对外暴露的端口,与父图中 SubGraphNode 的 ports 对应

PortDescriptor 字段说明

字段 类型 说明
id string 端口 ID,节点内唯一
direction string "input""output"
channels int 通道数;-1 表示继承
sampleRate int 采样率(Hz);-1 表示继承
label string UI 显示标签
required bool 是否为必连端口
{
  "type": "write_link",
  "payload": {
    "version": "2.2",
    "rootChainId": "root",
    "global": { "channels": 20, "sampleRate": 48000, "frameSize": 240 },
    "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": "mixer#1",
            "moduleType": "audio_mixer_v1",
            "order": 1,
            "enabled": true,
            "position": {"x": 400, "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","required":true}
            ]
          },
          {
            "instanceId": "group#1",
            "moduleType": "subgraph",
            "subGraphId": "eq_stage",
            "position": {"x": 700, "y": 200},
            "ports": [
              {"id":"input","direction":"input","channels":2,"sampleRate":48000,"label":"In","required":true},
              {"id":"output","direction":"output","channels":2,"sampleRate":48000,"label":"Out","required":true}
            ]
          }
        ],
        "edges": [
          {"id":"e1","fromModule":"gain#1","fromPort":"output","toModule":"mixer#1","toPort":"input_0","channels":2,"sampleRate":48000},
          {"id":"e2","fromModule":"mixer#1","fromPort":"output","toModule":"group#1","toPort":"input","channels":2,"sampleRate":48000}
        ]
      },
      "eq_stage": {
        "id": "eq_stage",
        "name": "EQ Stage",
        "externalPorts": [
          {"id":"input","direction":"input","channels":2,"sampleRate":48000,"label":"In","required":true},
          {"id":"output","direction":"output","channels":2,"sampleRate":48000,"label":"Out","required":true}
        ],
        "nodes": [
          {
            "instanceId": "eq#1",
            "moduleType": "common_eq_v1",
            "order": 0,
            "enabled": true,
            "position": {"x": 200, "y": 150},
            "ports": [
              {"id":"input","direction":"input","channels":-1,"sampleRate":-1,"label":"In","required":true},
              {"id":"output","direction":"output","channels":-1,"sampleRate":-1,"label":"Out","required":true}
            ]
          }
        ],
        "edges": []
      }
    }
  }
}

8. 消息流程图

8.1 正常参数设置流程

Frontend                 Backend                  DSP Hardware
   │                        │                         │
   │── set_param ──────────►│                         │
   │                        ├── Binary Frame (0x01) ──►│
   │◄─ set_param_ack ───────┤                         │
   │                        │                         │
   │              [其他客户端]                          │
   │                        ├── param_update ─────────►│ (广播)

8.2 链路下发流程

Frontend                 Backend                  DSP Hardware
   │                        │                         │
   │── write_link ─────────►│                         │
   │                        ├─ 持久化 JSON 到文件      │
   │                        ├─ ChainFlattener 展平子图  │
   │                        ├─ Binary Frame (0x02) ──►│
   │◄─ write_link_ack ──────┤                         │
   │                        │            [DSP 重建链路] │
   │── set_param (初始参数)──►│                         │
   │◄─ set_param_ack ───────┤                         │

8.3 客户端重连恢复流程

Frontend                 Backend
   │                        │
   │── WebSocket Connect ──►│
   │◄─ dsp_status ──────────┤  (后端主动推送当前 DSP 状态)
   │                        │
   │── read_link ───────────►│
   │◄─ read_link_ack ───────┤  (返回链路 JSON)
   │                        │
   │── get_all_params ──────►│  (针对每个模块实例)
   │◄─ get_all_params_ack ──┤

8.4 DSP 连接流程

Frontend                 Backend                  DSP Hardware
   │                        │                         │
   │── connect_dsp ─────────►│                         │
   │                        │── TCP/Serial Connect ───►│
   │◄─ connect_dsp_ack ─────┤                         │
   │◄─ dsp_status ──────────┤  (广播给所有客户端)      │

8.5 工作模式切换流程

Frontend                 Backend               其他客户端
   │                        │                      │
   │── set_mode ────────────►│                      │
   │                        ├─ 更新内部模式状态      │
   │◄─ mode_changed ────────┤                      │
   │                        ├── mode_changed ──────►│ (广播)

8.6 PC 仿真启动流程

Frontend                 Backend              DynamicChain.dll + NAudio
   │                        │                            │
   │── sim_start ───────────►│                            │
   │                        ├── 加载当前链路 JSON          │
   │                        ├── Init DLL + open audio ──►│
   │                        │                            │ (音频处理开始)
   │◄─ sim_status ──────────┤  (running=true, 广播)      │
   │                        │                            │
   │── sim_stop ────────────►│                            │
   │                        ├── Stop audio + unload ────►│
   │◄─ sim_status ──────────┤  (running=false, 广播)     │

8.7 调试订阅流程

Frontend                 Backend (DebugService)
   │                        │
   │── debug_subscribe ─────►│  (intervalMs=500)
   │                        ├─ 启动定时器
   │◄─ debug_metrics ───────┤  (每 500ms 推送一次)
   │◄─ debug_metrics ───────┤
   │◄─ debug_metrics ───────┤
   │         ...             │
   │── debug_unsubscribe ───►│
   │                        ├─ 停止定时器

8.8 WAV 抓取流程

Frontend                 Backend              DynamicChain.dll
   │                        │                       │
   │── wav_capture_start ───►│                       │
   │                        ├── Hook module output ─►│
   │◄─ wav_capture_ack ─────┤  (captureId="a3f2b1c0")│
   │                        │     [抓取 durationMs]   │
   │                        │◄─ 音频数据回调 ─────────┤
   │                        ├─ 写 WAV 文件            │
   │◄─ wav_capture_done ────┤  (fileSizeBytes=2097152)│
   │                        │                       │
   │── GET /api/debug/wav/a3f2b1c0 ──────────────────►│ (REST 下载)

8.9 子图导航流程(纯前端操作,不涉及 WebSocket)

子图导航流程(纯前端操作,不涉及 WebSocket):

用户双击 SubGraphNode      →  useChainStore.navigateInto(subGraphId)
                           →  currentChainId 更新为 subGraphId
                           →  ChainCanvas 重绘子图内容
                           →  BreadcrumbNav 显示导航路径

用户点击面包屑返回         →  useChainStore.navigateBack()
                           →  currentChainId 恢复为父图 id
                           →  ChainCanvas 重绘父图

用户点击保存/下发链路      →  前端发送 write_link(含完整 LinkConfig v2.2,含子图定义)
                           →  后端 ChainFlattener 展平子图
                           →  后端生成 set_link v3 二进制帧下发 DSP

子图导航是纯前端的视图切换行为,不产生任何 WebSocket 消息。前端通过 useChainStore 中的 currentChainId 状态决定 ChainCanvas 当前渲染哪个 ChainDefinition。只有在用户主动保存或下发链路时,才触发一次 write_link 消息,其 payload 包含完整的 LinkConfig v2.2(含所有子图定义)。


9. 消息类型汇总表

前端 → 后端

type 分组 说明
ping 基础通信 心跳探测
set_param 参数操作 设置单个参数
set_params 参数操作 兼容别名(与 set_param 等价)
get_param 参数操作 查询单个参数
get_all_params 参数操作 查询实例全量参数
write_link 链路管理 保存并下发链路配置;payload: LinkConfig v2.2(含子图定义)
read_link 链路管理 加载链路配置
connect_dsp DSP 连接 连接 DSP 硬件(TCP 或串口)
disconnect_dsp DSP 连接 断开 DSP 连接
get_status DSP 连接 查询后端及 DSP 状态
get_dsp_chain DSP 连接 从 DSP 读取链路结构(仅 test_verify)
set_mode 工作模式 切换工作模式
sim_start PC 仿真 启动 PC 端算法仿真
sim_stop PC 仿真 停止 PC 端算法仿真
list_audio_devices PC 仿真 枚举系统音频设备
debug_subscribe 调试 订阅调试指标周期推送
debug_unsubscribe 调试 取消订阅调试指标
wav_capture_start 调试 启动模块节点 WAV 抓取

多端口连线与子图对协议层透明:前端无需新增消息类型,所有结构变化均封装在 write_linkLinkConfig v2.2 payload 内。

后端 → 前端

type 分组 推送方式 说明
pong 基础响应 单播(请求方) 心跳响应
error 基础响应 单播(请求方) 错误响应
set_param_ack 参数响应 单播(请求方) 参数设置确认
get_param_ack 参数响应 单播(请求方) 参数查询响应
get_all_params_ack 参数响应 单播(请求方) 全量参数响应
param_update 参数响应 广播(其他客户端) 多端参数同步广播
write_link_ack 链路响应 单播(请求方) 链路保存确认
read_link_ack 链路响应 单播(请求方) 链路加载响应
get_dsp_chain_ack 链路响应 单播(请求方) DSP 链路读取响应
connect_dsp_ack DSP 状态 单播(请求方) DSP 连接结果
disconnect_dsp_ack DSP 状态 单播(请求方) DSP 断开确认
dsp_status DSP 状态 广播(所有客户端) DSP 状态推送
mode_changed 模式 广播(所有客户端) 模式变更广播
sim_status PC 仿真 广播(所有客户端) 仿真状态推送
audio_devices_ack PC 仿真 单播(请求方) 音频设备列表
debug_metrics 调试推送 单播(订阅方) 调试指标周期推送
log_stream 调试推送 广播(所有客户端) 实时日志流推送
wav_capture_ack 调试推送 单播(请求方) WAV 抓取启动确认
wav_capture_done 调试推送 单播(请求方) WAV 抓取完成通知

新增消息类型(v4.0 补充)

前端 → 后端:Preset 管理

// 保存当前模块参数为一套 preset
{ "type": "save_preset", "instanceId": "gain#1", "presetId": "bass_boost", "name": "Bass Boost" }
// 响应: preset_save_ack

// 加载 preset 参数到 UI
{ "type": "load_preset", "instanceId": "gain#1", "presetId": "bass_boost" }
// 响应: preset_data

// 列出某模块的所有 preset
{ "type": "list_presets", "instanceId": "gain#1" }
// 响应: preset_list

// 删除 preset
{ "type": "delete_preset", "instanceId": "gain#1", "presetId": "bass_boost" }
// 响应: preset_delete_ack

前端 → 后端:Profile 管理

// 保存 ambiance profile
{
  "type": "save_profile",
  "profileId": "hifi_mode", "name": "HIFI",
  "modulePresets": { "gain#1": "flat", "eq#1": "hifi_eq", "delay#1": "default" }
}
// 响应: profile_save_ack

{ "type": "list_profiles" }            // 响应: profile_list
{ "type": "delete_profile", "profileId": "hifi_mode" }  // 响应: profile_delete_ack

// 切换音效模式(向 DSP 发 CMD=0x05)
{ "type": "set_ambiance", "ambianceIndex": 2 }
// 响应: set_ambiance_ack

前端 → 后端:ParamBin 生成

// 将指定 profiles 编译为 ParamBin 并下发 DSP(CMD=0x04)
{
  "type": "generate_param_bin",
  "targetProfiles": ["hifi_mode", "dynamic_mode", "rock_mode"],
  "modulePresetLimits": { "gain#1": 3, "eq#1": 3, "delay#1": 2 }
}
// 响应: param_bin_ack

前端 → 后端:多维参数 set_param(新增 dimIndices)

{
  "type": "set_param",
  "instanceId": "eq#1",
  "paramId": "bandFreq",
  "dimIndices": [2, 5],    // 新增可选字段:dim0=channel=2, dim1=band=5
  "value": "120.0"
}
// 如果 dimIndices 不存在则向后兼容(使用旧 channel 字段)
// 后端编码:channelIdx = (2<<8)|5 = 0x0205

前端 → 后端:子图同步校验

// 触发后端向 DSP 查询平坦模块列表(仅校验,不重建层次结构)
{ "type": "read_dsp_chain" }
// 响应: dsp_chain_echo + 可能跟随 chain_sync_warning

注:get_dsp_chain 行为变更 —— 返回 Backend 存储的 LinkConfig(含子图),不询问 DSP:

{ "type": "get_dsp_chain" }
// 响应: dsp_chain(LinkConfig v2.2,含子图层次结构,来自 current_link.json)


后端 → 前端:新增响应消息

// Preset 响应
{ "type": "preset_save_ack", "instanceId": "gain#1", "presetId": "bass_boost" }
{ "type": "preset_data", "instanceId": "gain#1", "presetId": "bass_boost",
  "params": { "gainDb[0]": "3.0", "gainDb[1]": "-1.5", "bandFreq[2][5]": "120.0" } }
{ "type": "preset_list", "instanceId": "gain#1",
  "presets": [{"presetId":"flat","name":"Flat"},{"presetId":"bass_boost","name":"Bass Boost"}] }
{ "type": "preset_delete_ack", "instanceId": "gain#1", "presetId": "bass_boost" }

// Profile 响应
{ "type": "profile_save_ack", "profileId": "hifi_mode" }
{ "type": "profile_list",
  "profiles": [{"profileId":"hifi_mode","name":"HIFI"},{"profileId":"dynamic","name":"动感"}] }
{ "type": "profile_delete_ack", "profileId": "hifi_mode" }

// Ambiance 响应
{ "type": "set_ambiance_ack", "ambianceIndex": 2 }
{ "type": "param_bin_ack", "byteSize": 4096, "numAmbiances": 3, "numModules": 8 }

// 同步校验响应
{ "type": "dsp_chain_echo",
  "modules": [{"instanceId":"gain#1","typeId":"channel_gain_v1"},
               {"instanceId":"eq#1","typeId":"common_eq_v1"}] }
{ "type": "chain_sync_warning",
  "missing": ["group#1.eq#1"], "extra": [],
  "message": "DSP 模块列表与存储配置不匹配,建议重新下发链路" }

更新的 DSP Binary 帧格式

set_param CMD=0x01(v3,数字 ID)

旧版(字符串):[CMD=0x01][LEN]["gain#1.gainDb#2\0"][f32] ≈ 30 字节
新版(数字):  [CMD=0x01][LEN=9][moduleIdx:u8][paramTypeId:u16LE][channelIdx:u16LE][type:u8=0x01][value:f32LE]
              = 14 字节(节省 53%)

channelIdx 编码:
  标量/全局:0x0000
  1D(通道 2):0x0002
  2D(通道2 band5):0x0205  ← (2<<8)|5

load_param_bin CMD=0x04

[CMD=0x04][DATA_LEN:u32LE][ ParamBin 数据 ]

ParamBin 内部格式:
  Header (8B):       "PCFG" + version:u8 + numAmbiances:u8 + numModules:u8 + reserved:u8
  Module Table:      numModules × 24B = instanceId:char[16] + typeNumId:u32 + numPresets:u8 + pad:u8[3]
  Ambiance Table:    numAmbiances × (16+numModules)B = name:char[16] + presetIndices:u8[numModules]
  Param Data:        per module per preset:
                       paramCount:u16 + (paramTypeId:u16 + channelIdx:u16 + value:f32)[paramCount]

set_ambiance CMD=0x05

[CMD=0x05][DATA_LEN=1:u32LE][ambianceIdx:u8] = 6 字节
DSP 响应:[CMD=0x85][DATA_LEN=1][ambianceIdx:u8]

get_chain CMD=0x06(用于同步校验)

请求:[CMD=0x06][DATA_LEN=0:u32LE] = 5 字节
响应:[CMD=0x86][DATA_LEN:u32LE][numModules:u8][pad:u8[3]]
      per module (32B): instanceId:char[24] + typeNumId:u32 + pad:u8[4]

paramTypeId 编号规范

格式:u16(高字节=模块类别,低字节=参数序号)

模块类别:
  0x01 = gain    0x02 = filter/EQ    0x03 = delay
  0x04 = dynamics  0x05 = mixer     0x06 = routing

已定义示例:
  0x0101 Gain.gainDb      0x0102 Gain.mute      0x0103 Gain.enable
  0x0201 EQ.bandFreq      0x0202 EQ.bandGain    0x0203 EQ.bandQ
  0x0204 EQ.bandType      0x0205 EQ.bandEnable  0x0206 EQ.moduleEnable
  0x0301 Delay.delaySamples  0x0302 Delay.enable


新增消息类型(v5.1 补充)

2.2 参数操作(续)

apply_params — 前端主导批量参数推送

前端将 preset 参数应用到自身状态后,通过此消息批量推送给后端。后端写入 paramStore 并向 DSP 下发每个参数。

{
  "type": "apply_params",
  "instanceId": "gain#1",
  "params": {
    "gainDb#0": "-3.5",
    "gainDb#1": "-3.5",
    "mute#0":   "false",
    "mute#1":   "false",
    "enable":   "true"
  }
}
字段 类型 必填 说明
instanceId string 模块实例 ID
params object 参数键值对;key 为含通道后缀的 paramId(如 "gainDb#0"),value 为字符串

后端行为: 1. 遍历 params,对每条记录:写入 paramStore["{instanceId}.{paramId}"],并异步调用 TryFlushToDspAsync 2. 所有布尔值经 ToStoreString() 确保以小写 "true" / "false" 存储 3. 不广播 param_update(批量覆盖场景,广播由前端自行决定是否补发)

响应apply_params_ack

{
  "type": "apply_params_ack",
  "instanceId": "gain#1",
  "success": true
}

set_param 对比

特性 set_param apply_params
参数数量 单个 批量
广播 param_update
主要用途 UI 实时调参 Preset 加载后批量推送
响应类型 set_param_ack apply_params_ack

get_module_params — 从后端读回参数(回读校验)

前端"Read from backend"操作,用于校验后端 paramStore 中的当前值是否与前端期望一致。

{
  "type": "get_module_params",
  "instanceId": "gain#1"
}
字段 类型 必填 说明
instanceId string 模块实例 ID

后端行为:直接从 paramStore 中过滤该实例的所有参数键,去掉 "{instanceId}." 前缀后返回。不与 DSP 通信。

响应module_params

{
  "type": "module_params",
  "instanceId": "gain#1",
  "params": {
    "gainDb#0": "-3.5",
    "gainDb#1": "-3.5",
    "mute#0":   "false",
    "mute#1":   "false",
    "enable":   "true"
  }
}

所有值均为字符串,布尔参数保证为小写 "true" / "false"


2.7 Preset 管理(更新)

save_preset — 保存模块参数快照(已更新)

{
  "type": "save_preset",
  "instanceId": "gain#1",
  "presetId": "bass_boost",
  "name": "Bass Boost",
  "params": {
    "gainDb#0": "3.0",
    "gainDb#1": "3.0",
    "mute#0":   "false",
    "enable":   "true"
  }
}
字段 类型 必填 说明
instanceId string 模块实例 ID
presetId string Preset 标识符(文件名)
name string 可读名称(UI 显示)
params object 前端提供的参数快照(前端权威模式);若省略则后端从 paramStore 快照(向后兼容)

后端行为(当 params 字段存在时): 1. 将 params 中每条记录写入 paramStore(经 ToStoreString() 保证布尔小写) 2. 为每条记录调用 TryFlushToDspAsync 异步下发 DSP 3. 将完整 preset(含 params 字段)序列化保存到 data/presets/{instanceId}/{presetId}.json

后端行为(params 字段缺失时,向后兼容): - 从 paramStore 中快照该实例的当前参数并保存文件

响应preset_save_ack


list_presets — 列出模块的所有 preset(已更新)

{ "type": "list_presets", "instanceId": "gain#1" }

响应preset_list(现在每个条目包含 params 字段)

{
  "type": "preset_list",
  "instanceId": "gain#1",
  "presets": [
    {
      "presetId": "flat",
      "name": "Flat",
      "params": {
        "gainDb#0": "0.0",
        "gainDb#1": "0.0",
        "mute#0":   "false",
        "enable":   "true"
      }
    },
    {
      "presetId": "bass_boost",
      "name": "Bass Boost",
      "params": {
        "gainDb#0": "3.0",
        "gainDb#1": "3.0",
        "mute#0":   "false",
        "enable":   "true"
      }
    }
  ]
}

更新说明:原版 preset_list 每个条目只含 presetIdname(元数据)。新版从文件中读取完整 preset 并在列表条目中包含 params 字段。前端在启动时收到 preset_list 后,将 params 存入本地 presetDataStore[instanceId][presetId],后续 preset 加载可直接通过 apply_params 推送,无需再发 load_preset 往返请求。


3. 后端 → 前端 消息(v5.1 新增)

apply_params_ack — 批量参数应用确认

{
  "type": "apply_params_ack",
  "instanceId": "gain#1",
  "success": true
}

module_params — 后端参数回读响应

{
  "type": "module_params",
  "instanceId": "gain#1",
  "params": {
    "gainDb#0": "-3.5",
    "gainDb#1": "-3.5",
    "mute#0":   "false",
    "enable":   "true"
  }
}

params 对象的 key 为不含 instanceId. 前缀的 paramId,value 均为字符串;布尔值保证小写。


9. 消息类型汇总表(v5.1 增量)

在原有汇总表基础上,新增以下条目:

前端 → 后端

type 分组 说明
apply_params 参数操作 批量写入 paramStore 并下发 DSP;前端主导 preset 加载
get_module_params 参数操作 从 paramStore 读回指定实例的全部参数,用于前端回读校验

后端 → 前端

type 分组 推送方式 说明
apply_params_ack 参数响应 单播(请求方) 批量参数应用确认
module_params 参数响应 单播(请求方) 后端参数回读响应

已更新消息

type 方向 变更说明
save_preset 前端→后端 新增可选 params 字段;前端可直接传参数快照(前端权威模式),省略时后端从 paramStore 快照(向后兼容)
preset_list 后端→前端 每个 preset 条目新增 params 字段,前端可在启动时预加载所有 preset 数据

参数值规范说明(v5.1)

所有经 WebSocket 传输并存入 paramStore 的布尔型参数值,统一使用小写字符串:

值类型 正确形式 错误形式(旧 C# 默认)
布尔真 "true" "True"
布尔假 "false" "False"

这一规范确保前端 JavaScript 使用 === 'true' 进行布尔比较时能够正确匹配,无需额外做大小写转换。后端通过 ToStoreString() 辅助函数在所有写入路径(HandleSetParamHandleApplyParamsHandleSavePresetHandleLoadPresetHandleSetAmbiance)统一保证此规范。