piano-ddsp-pytorch 项目中实现的 PyTorch 训练模型model-suite-v1.0.1,部署合同为 FP32、opset 13、batch 1、每调用 1 帧、250 Hz、16 kHz、最多 16 声部
samples/case3/
├── prepare_piano_ddsp_models.py model release + ATC args
├── pyacl_ddsp.py Feature/Control ACL contract
├── piano_ddsp_runtime/worker.py realtime state + scheduling
├── midi_ddsp_realtime.py MIDI-DDSP offline renderer
└── webui/src/App.tsx browser workbench
| 输入 | 形状 | 含义 |
|---|---|---|
conditioning |
[1,1,16,2] |
16 声部的音高与力度条件 |
pedal |
[1,1,4] |
踏板与连续控制状态 |
piano_model |
[1] |
MAESTRO 钢琴年份索引 |
extended_pitch |
[1,1,16,1] |
扩展音高条件 |
context_state |
[1,1,64] |
全局循环状态 |
monophonic_state |
[1,16,192] |
每声部循环状态 |
每次推理只处理一个控制帧,帧间递归关系通过状态输入输出显式传递。
| 输出 | 形状 | 含义 |
|---|---|---|
amplitudes |
[1,1,16,1] |
声部总幅度 |
harmonic_distribution |
[1,1,16,96] |
96 个谐波比例 |
inharmonicity、f0_hz |
[1,1,16,1] |
非谐性和基频 |
noise_magnitudes |
[1,1,16,64] |
64 个噪声频带 |
reverb_ir |
[1,24000] |
学习到的混响冲激响应 |
next_context_state |
[1,1,64] |
下一帧全局状态 |
next_monophonic_state |
[1,16,192] |
下一帧声部状态 |
导出验证需要连续回灌状态,逐项对照 PyTorch 与 ONNX 的控制量和循环状态,不能只比较孤立单帧。
以下片段来自 prepare_piano_ddsp_models.py:
RELEASE = "model-suite-v1.0.1"
PRECISION_MODE_V2 = "origin"
ATC_COMPILE_ENVIRONMENT = {
"MULTI_THREAD_COMPILE": "0",
"TE_PARALLEL_COMPILER": "1",
}
INPUT_SHAPE = (
"conditioning:1,1,16,2;pedal:1,1,4;piano_model:1;"
"extended_pitch:1,1,16,1;context_state:1,1,64;"
"monophonic_state:1,16,192"
)
脚本构造的 ATC 命令固定使用 --input_format=ND、--precision_mode_v2=origin、--enable_graph_parallel=0 和 --log=info;此代码只在真实 Ascend 板端执行,非 ARM 或无 atc 环境会直接拒绝。
仅在 Ascend 310B 板端激活已有 CANN 环境后执行:
python prepare_piano_ddsp_models.py --variant gru-unrolled --models gru_ir_96_64
python tools/validate_piano_ddsp_om.py \
--bundle models/piano_ddsp/bundles/model-suite-v1.0.1-gru-unrolled-fp32-origin/manifest.json \
--model-id gru_ir_96_64 \
--reference models/piano_ddsp/references/model-suite-v1.0.1/gru_ir_96_64/reference-10000.npz \
--report reports/piano-ddsp/gru-ir-96-64-10000.json --frames 10000 --activate
短于 10,000 帧的冒烟报告不能激活模型;catalog 和 worker 也会拒绝没有合格报告的 OM。CANN 8.0.0 的原生 DynamicGRUV2 不接受 FP32 输入,因此 FP32 基线使用已经与原始 ONNX 连续逐帧对照 10,000 帧的 gru-unrolled 变体。
32 个 note/rest token 上下文预测 6 维表情控制:volume、vol_fluc、vibrato、brightness、attack、vol_peak_pos64 帧窗口、250 Hz 控制率生成音频控制量1/2/4/8 推理并混音model_bundle_id,不能从不同导出批次分别组合 Expression 与 Synthesis来自 doc/05-midi-ddsp-realtime.md:
python midi_ddsp_realtime.py \
--midi midi/ddsp-test.mid \
--model-bundle models/midi_ddsp/bundles/google-urmp-stateful-v2-batched-origin/manifest.json \
--instrument-id 0 \
--seed 20260724 \
--render-only \
--tail-seconds 2 \
--output reports/midi_ddsp/ddsp-test.wav \
--report reports/midi_ddsp/ddsp-test.json
去掉 --render-only 即可在完整渲染后播放。F0 使用官方 top-p=0.95 采样,外部 Gumbel 张量由默认种子 20260724 生成;休止帧 F0 为 0 Hz。报告记录输入、模型包、组件、种子、WAV 峰值/RMS、削波、underrun 和 overrun。
Feature OM 运行时路径为 models/om/ddsp_vst_feature_mixed_float16.om,固定合同:
audio float32[1024]
f0_scaled, pw_scaled, f0_hz, pw_db float32[1]
sample_rate=16000, hop_size=320
Control OM 固定输入输出:
state[512], f0_scaled[1], pw_scaled[1]
amplitude[1], harmonics[60], noise_amps[65], state_out[512]
Control 模型覆盖巴松、单簧管、长笛、口风琴、萨克斯、西塔琴、长号、小号、大号、小提琴和人声音色,共 11 类;不同音色共享同一输入与状态合同。
固定链路为:
48 kHz 双声道摄像头输入 -> 单声道 -> 16 kHz -> 1024 窗/320 步长
-> Feature OM -> Control OM -> DDSP 合成 -> 48 kHz 双声道输出
parec、paplay 对应的 PulseAudio source/sink,不使用 monitor-18 dB;持续过载会进入明确的安全静音pyacl_ddsp.py 固定静态张量形状:
INPUT_SHAPES = {
"state": (512,),
"f0_scaled": (1,),
"pw_scaled": (1,),
}
OUTPUT_SHAPES = {
"amplitude": (1,),
"harmonics": (60,),
"noise_amps": (65,),
"state_out": (512,),
}
PyACL 生命周期是板端独占步骤:acl.init、rt.set_device、create_context、mdl.load_from_file、创建 dataset、mdl.execute、memcpy 回宿主端,最后按逆序释放 buffer、dataset、desc、model、context 和 device。
pyacl_ddsp.py 的推理入口先校验输入再调用 OM:
prepared: dict[str, np.ndarray] = {}
for name, shape in self.input_shapes.items():
array = np.ascontiguousarray(inputs[name], dtype=np.float32)
if array.shape != shape:
raise ValueError(
f"Unexpected input {name} shape: {array.shape}, expected {shape}"
)
if not np.all(np.isfinite(array)):
raise ValueError(f"Input {name} contains NaN or Inf")
prepared[name] = array
随后在锁内设置 context、acl.rt.memcpy 拷贝输入、acl.mdl.execute 推理、再 memcpy 拷回输出。state_out 必须由调用方回灌到下一帧,形成闭环状态。
realtime_ddsp.py 中每个 MIDI voice 持有独立控制模型状态与合成器:
snapshots = self.midi.next_snapshots()
if not snapshots:
self.voice_gain.reset()
return np.zeros(MODEL_HOP_SIZE, dtype=np.float32)
mixed = np.zeros(MODEL_HOP_SIZE, dtype=np.float32)
rendered_voices = 0
for snapshot in snapshots:
voice = self.voices[snapshot.slot]
mixed += voice.render(snapshot, settings)
if not snapshot.finished:
rendered_voices += 1
return self.voice_gain.process(mixed, rendered_voices)
音频流顺序是:MIDI 快照 -> 每声部 OM 控制量 -> 谐波合成 + 滤波噪声 -> 多声部混音 -> 线性重采样到 48 kHz -> 增益 -> FreeVerb 混响 -> 输出块。当前用户入口为 WebUI;realtime_ddsp.py 是已退役的历史 CLI,用于理解同一类状态和音频边界。
| 操作 | 开发电脑 | Ascend 310B |
|---|---|---|
| 编辑源码、运行普通 Python 单元测试 | 是 | 否 |
npm ci、npm run test、npm run build |
是 | 否 |
| 下载并校验发布模型 | 是 | 可接收已校验资产 |
CANN 环境、ATC、OM、PyACL、npu-smi |
否 | 是 |
| ALSA、PulseAudio、真实 MIDI、听音验收 | 否 | 是 |
ATC、OM、PyACL、ALSA、MIDI 和音频设备操作都属于板端独占步骤,必须在真实 Ascend 310B 上执行。
Piano-DDSP、DDSP-VST 和 MIDI-DDSP 的 ONNX/OM 均从 zhouxzh/piano-ddsp-ascend310 已发布 release 获取:
# 默认是锁定的 Piano-DDSP release。
python tools/download_model_release.py
# 其他模型族使用固定 revision、目录和 manifest SHA256。
python tools/download_model_release.py \
--revision <immutable-release> --release-dir <published-directory> \
--target-dir models/<family> --manifest-sha256 <sha256-of-SHA256SUMS>
下载器先读取固定 revision 的 SHA256SUMS,再断点下载并逐项校验。下载失败、manifest SHA256 不符或任何资产哈希不符时必须停止;不要用移动的分支名替代发布 revision。
在板端 case3 根目录、已激活 CANN 环境中执行:
python prepare_piano_ddsp_models.py --variant gru-unrolled --models gru_ir_96_64
脚本会在 bundle 中写入 manifest.json、每个模型的 .atc.log、command.json、OM 和元数据。转换命令保留 MULTI_THREAD_COMPILE=0、TE_PARALLEL_COMPILER=1、enable_graph_parallel=0,以单线程、无并行方式执行 ATC,控制板端编译温度和内存。先转换并验证 gru_ir_96_64,之后才允许转换其他模型。
文档中的默认命令:
cd ~/Documents/case3
bash tools/convert_onnx_to_om.sh
默认输入模型和固定输入形状为:
model=models/ddsp_vst/Violin.onnx
state:512;f0_scaled:1;pw_scaled:1
成功或失败后都会保留 models/om/Violin.om、Violin.atc.log 和 Violin.atc.summary.txt。其他音色使用相同图结构,仍应逐个检查各自的 .atc.summary.txt,不能仅根据小提琴模型的结果判定全部模型已经转换成功。
脚本支持替换模型、输出路径、输入形状和 SOC 版本:
bash tools/convert_onnx_to_om.sh \
--model models/ddsp_vst/Flute.onnx \
--output models/om/Flute \
--input-shape 'state:512;f0_scaled:1;pw_scaled:1' \
--soc-version Ascend310B1
文档中的 20T 示例使用 Ascend310B1;本教程目标为 Ascend310B4 / 8T 时,应以板端 npu-smi info 和 CANN 文档确定的 soc_version 为准,不要照搬其他设备的芯片名。
先在本地开发环境生成确定性 ONNX 基准:
python tools/compare_onnx_om_precision.py reference \
--onnx models/ddsp_vst/Violin.onnx \
--output reports/Violin_onnx_reference_1024.npz \
--steps 1024 --seed 20260721
将基准同步到开发板后,在板端 Anaconda base 环境运行 OM 对比:
source /usr/local/miniconda3/etc/profile.d/conda.sh
conda activate base
source /usr/local/Ascend/ascend-toolkit/set_env.sh
cd ~/Documents/case3
python tools/compare_onnx_om_precision.py om \
--om models/om/Violin.om \
--reference reports/Violin_onnx_reference_1024.npz \
--report reports/Violin_fp16_precision_1024.json \
--device 0
teacher-forced 模式每帧使用 ONNX 状态输入,隔离单次转换误差;closed-loop 模式把 OM 自己的 state_out 回灌到下一帧,检查状态误差是否累积。数值对照是独立于听音的评价,不等价于音质验收。
ip -4 address
lsusb
pactl list short sinks
pactl list short sources
cat /proc/asound/cards
cat /proc/asound/seq/clients
aplay -l
arecord -l
这些命令只描述板端设备状态。pactl list short sources 中,麦克风应显示为实体 capture source;名称以 .monitor 结尾的条目是输出回放监视源,不能用于 DDSP-VST 或麦克风输入测试。
EDIFIER M16 Pro USB 喇叭优先使用稳定 ALSA card ID:
amixer -c Pro scontrols
amixer -c Pro sget PCM
amixer -c Pro set PCM 75% unmute
speaker-test -D plughw:CARD=Pro,DEV=0 -c 2 -t wav -l 1
aplay -Dplughw:CARD=Pro,DEV=0 speaker-test-stereo.wav
板载 3.5mm 只做厂商单声道诊断:
amixer set Playback 10
amixer set Deviceid 2
aplay -Dhw:ascend310b speaker-test-mono.wav
板载接口和 ALSA 驱动宣告双声道能力,但当前镜像下双声道模拟输出没有通过音质、实时传输和可停止性验证,不作为实时立体声验收路径。蓝牙 A2DP 会引入编码、无线传输和播放缓冲延迟,不应把蓝牙链路延迟归因于 NPU 推理。
/dev/snd/seq 或 raw MIDI 设备读取实体键盘,浏览器 Web MIDI 不是板端输入来源F2-C5,支持力度和 CC64 延音python realtime_ddsp.py --list-midi
python realtime_ddsp.py --list-audio
当前生产用户入口是 WebUI 的“MIDI 键盘”模式:服务端端口下拉框绑定板端枚举的输入端口,运行中不能换端口,断开时释放该来源音符。
在开发电脑从仓库根目录执行:
cd samples\case3
python -m pip install -r requirements.txt
python -m pytest -q
cd webui
npm ci
npm run test
npm run build
npm run test:e2e
部署脚本在暂存位置完成程序、前端资源和模型元数据的一致性检查,再原子切换运行版本:
powershell -ExecutionPolicy Bypass -File tools/deploy_midi_ddsp_webui.ps1
开发板不安装 Node 或 npm,也不运行 Vite 生产服务器,只接收开发电脑生成的 webui/dist/。
在板端已有 conda base 与 CANN 环境中启动:
source /usr/local/miniconda3/etc/profile.d/conda.sh
conda activate base
source /usr/local/Ascend/ascend-toolkit/set_env.sh
cd /home/HwHiAiUser/Documents/case3
python scripts/run_webui.py
服务默认监听 0.0.0.0:8765。本机冒烟检查:
curl -fsS http://127.0.0.1:8765/
curl -fsS http://127.0.0.1:8765/api/v1/status
板端触摸屏全屏打开:
DISPLAY=:0 XAUTHORITY=/home/HwHiAiUser/.Xauthority \
firefox --kiosk http://127.0.0.1:8765
| 工作区 | 关键内容 | 验收要点 |
|---|---|---|
| 实时演奏 | 触摸屏与实体 MIDI 共享同一 Piano-DDSP 会话 | 统一会话、共享音色与资源锁;运行中锁定输入模式 |
| MIDI-DDSP | 音频库与新建渲染 | 一个文件卷帘;WAV 版本、报告、播放目标同步 |
| DDSP-VST | 麦克风 Effect | 只接受实体 capture;Feature/Control 后端必须是 acl/om |
| 设备 | 概览、音频设备、运行环境 | NPU、模型、输出、Capture、MIDI 和依赖可见 |
实时状态采用 WebSocket 传递音符边沿、作业进度和实时指标;REST 查询提供模型、设备和会话快照;文件访问承载 MIDI、WAV、录音与评价报告。
在已激活 CANN 和 Conda base 的板端可运行:
python tools/validate_webui_runtime.py
该命令用于板端环境与真实运行时验证,不等价于听音验收。真实音频验收至少记录推理耗时、队列延迟、总延迟、PCM 非零、削波、capture overflow 和 playback underrun;仅有 HTTP 200 不能证明发声正确。
realtime_ddsp.py 用有界队列把渲染 worker 与音频回调解耦:
try:
block = self.blocks.get_nowait()
except queue.Empty:
outdata.fill(0.0)
had_underrun = True
else:
self.space_available.set()
block = np.asarray(block, dtype=np.float32)
if block.ndim == 1:
outdata[:, :] = block[:, np.newaxis]
elif self.output_channels == 1:
outdata[:, 0] = np.mean(block, axis=1)
渲染 worker 生成块,音频回调按设备通道数分发;队列为空时填零并计 underrun,队列满时渲染线程计 overrun。短缓冲提高按键响应但更敏感,长缓冲提高连续性但增加反馈延迟,因此缓冲长度和系统音量、合成增益要分开评价。
run_webui.py,确认首页与 /api/v1/statuslinux/case3/model-chain.md:三条模型链、模型来源、输入输出合同与命令记录linux/case3/piano-output.md:触控/MIDI 演奏、实时会话和音频冒烟记录linux/case3/audio-smoke.md:ALSA/PulseAudio/设备检查、扬声器测试和音量记录0.0.0.0:8765 启动,/ 与 /api/v1/status 返回正常,kiosk 可打开panic、CC64 和断线释放后无悬挂音符acl/om,PCM 非零,underrun/overflow/clipping 有记录;600 秒长稳协议单独评价