dsh-cover
用你的音色一句话翻唱一首歌。纯 Host 插件,零 npm 运行时依赖;所有 DSP 走 runtime/*.py,由 Applio 的 Python 解释器执行。
你要说的话
用我的音色翻唱一首「山水之间」
或者给链接、给本地文件:
用 myvoice 翻唱 https://www.bilibili.com/video/BV1Vb421B7XQ
用 myvoice 翻唱 D:\音乐\山水之间.mp3
Agent 会调 cover_make,跑完九个阶段,把 WAV 和 320k MP3 发给你。
取源怎么走的
source 三种形态,优先级从高到低:
- 本地文件存在 → 直接采纳,不复制、不转码
- http(s) 链接 → yt-dlp 下载;域名含
bilibili.com记作bilibili - 歌名关键词 → 按
fetchOrder(默认inmp3,bilibili,youtube)依次试:- inmp3(IN音乐网):站内搜索
https://inmp3.com/so/<关键词>/1/拿data-rid, 打开歌曲页取let code = "base64"解出酷我直链,直接下 mp3/aac 再用 ffmpeg 转 WAV。 不需要 yt-dlp、不需要 cookie,命中率最高,但源多数是 128k / 96~98k AAC。 - bilibili:网页搜索 API 拿 bvid → yt-dlp
-f bestaudio[ext=m4a], 30280 音轨通常 184~263k,音质最好。 - youtube:
ytsearch1:兜底,常被 bot 校验挡住。
- inmp3(IN音乐网):站内搜索
码率自动择优:inmp3 拿到的源码率低于 fetchMinKbps(默认 160)时,
会自动再试一次 B 站;只有当 B 站那份比它高 8 kbps 以上才替换,
否则删掉临时文件保留 inmp3。整个过程记在输出的 fallback_log 里。
实测(2026-11-04):
| 歌 | inmp3 | B 站 | 最终 |
|---|---|---|---|
| 晴天 周杰伦 | 128k mp3 | 184k m4a | bilibili 184k |
| 起风了 | 98k aac | 263k m4a | bilibili 263k |
| 山水之间 许嵩 | 128k mp3 | — | inmp3 128k |
YouTube 的搜索接口现在会被 bot 校验挡住(Sign in to confirm you're not a bot),
直链能下、搜索不能。真要搜 YouTube,加配置 cookiesFromBrowser: "chrome",
或者直接贴 YouTube 链接。
关键词取源时,目录名用关键词本身(如 晴天 周杰伦),不用 B 站标题
——B 站标题常带「【Hi-Res无损】|《…》」这类装饰,会把目录名弄得没法看。
真正的命中标题记在 run.json 的 fetch.picked.title 里。
三个工具
| 工具 | 作用 |
|---|---|
cover_doctor |
环境自检:Python/core.py/分离器/ffmpeg/yt-dlp/音色表/显卡/磁盘。不改文件。 |
cover_make |
全流程出成品:取源 → 分轨 → 定调 → 移调 → 换声 → 对齐 → 混音 → 验收 → 导出 |
cover_status |
读 run.json,看进度、参数、闸门结果、成品路径 |
九阶段与产物
<coversRoot>\<歌名>\
run.json 每阶段状态与耗时,断了可以 resume
src\ 取源(URL → WAV)
sep\wav\ UVR-MDX 分出来的 Vocals / Instrumental
sep\instrumental_shiftN.wav 降调后的伴奏(|pitch|≥1.5 且非 0 才生成)
conv\vocal_<voiceId>.wav 换声结果(44.1 kHz 单声道)
mix\report.json 三档变体的参数与 QC 实测值
mix\gates.json 闸门逐项判定
out\*_标准版.wav 成品 WAV(三档 + 干声)
out\*.mp3 320k MP3
out\provenance.json 模型 hash / 定调依据 / 对齐结果 / 闸门结论
音频只写 out/,mix/ 只留两个 JSON——同一份 69 MB 的 WAV 不会存两遍。
定调怎么定的
不用先跑一遍推理猜。拿你音色的训练数据 f0 分布当中位基准(_voices/<voiceId>.json 里的 f0_median_hz),和原唱人声的 f0 中位比:
半音数 = 12 × log2(音色中位 / 原唱中位)
高位超出音色上限 3.5 半音以上 → 降调;低位不够 → 升调;否则保持 0。--pitch 可以硬覆盖。
拿到半音数后按你文档第 3 节的判据分档:
| |pitch| | 模式 | 做法 |
|---|---|---|
| < 1.5 | none | 取 0,不动 |
| ≥ 1.5 | both | 人声 --pitch + 伴奏 asetrate/atempo 一起移 |
没有「只动人声」这档了:人声一旦移调、伴奏留在原调,整首歌必然跑调。早期版本留了个 vocal-only 档,实测就是错的,已删除。
实测:short.wav 强行 pitch:-5 → 模式 both,伴奏生成 instrumental_shift-5.wav(44.1 kHz 双声道 60.0021s),换声后中位 166.02 Hz(E3)。
长歌怎么办(4 GB 显卡的硬约束)
convert.py 默认按 --chunk-seconds 120 切块,切点吸附到 ±8 秒窗口里能量最低的帧(换气/间奏),块间 0.3 秒重叠做等功率交叉淡化;单块失败自动对半再切,下限 20 秒。
为什么必须切:整首 4 分半的人声一次喂进 RVC,cuDNN 会抛
RuntimeError: GET was unable to find an engine to execute this computation
——那是 CUDA 侧 ALLOC_FAILED 被 torch 翻译成的说法。实测 4m12s 能过、4m30s 必崩,就是显存峰值贴着 4 GB 上限。
采样率必须显式重采样(这是个真 bug,修过一次):Applio 输出的是音色模型自己的采样率(取决于音色模型,常见 40000 或 48000 Hz),不是输入文件的采样率。早期版本把 40000 Hz 的样本当 44100 Hz 用,等于每块都被压快 0.907 倍再补零,拼起来整首歌是碎的——表现出来就是"对齐偏差 1800ms、相关 0.36"。现在读回来先 librosa.resample 到 sr 再拼。
续跑怎么判断能不能沿用
run.json 每个阶段都带 inputs 指纹(文件大小 + mtime,换声那步还带 pitch/音色/分块参数)。指纹对不上就重算,不会拿旧结论套新产物。老 run.json 没有 inputs 字段的一律视为过期。
混音参数
三档默认值,实测能复现(峰值 −0.58 / RMS −13.1 / crest 12.5):
| 变体 | 人声增益 | 伴奏增益 | 混响 wet |
|---|---|---|---|
| standard 标准版 | +4.0 dB | −2.0 dB | 0.11 |
| forward 人声靠前版 | +6.5 dB | −3.0 dB | 0.09 |
| blend 融合版 | +3.0 dB | −1.5 dB | 0.15 |
母带顺序固定:先提增益到 −13 dBFS RMS,再 tanh 软限幅(ceiling 0.94 / knee 0.65)。反了会把动态压平。
闸门
| 项 | 限 |
|---|---|
| peak | ≤ −0.3 dBFS |
| rms | −16.0 ~ −10.0 dBFS |
| crest | ≥ 10.0 dB |
| clipped | = 0 |
| stereo 相关 | 0.10 ~ 0.995(≥0.99 视为近单声道,放行) |
4.5–9.5 kHz 的窄带哨音指标不参与判定:实测你自己的成品在它下面 96% 的帧都触发,那个指标只在纯人声轨上有点意义,现在只作为参考值输出。
闸门不过默认不导出(force: true 可强制)。干声轨不参与成品闸门。
装/卸
dsh plugin --profile desktop add github:lol9706262-source/dsh-cover
或从本地目录装:
dsh plugin --profile desktop add file:C:/path/to/dsh-cover
装完重启一次 DSH(主进程,不是开新会话)——Host 插件在进程启动时加载。
卸载:dsh plugin --profile desktop remove dsh-cover。
音色表
<voicesDir>\<voiceId>.json,一个音色一个文件,key 就是音色 id:
{
"myvoice": {
"pth": "<applioRoot>\\logs\\myvoice\\myvoice_300e_27900s.pth",
"index": "<applioRoot>\\logs\\myvoice\\myvoice.index",
"sample_rate": 40000,
"f0_median_hz": 172.89,
"f0_p95_hz": 258.75,
"f0_min_hz": 53.63,
"f0_max_hz": 319.34,
"defaults": { "index_rate": 0.75, "protect": 0.33, "volume_envelope": 1.0, "f0_method": "rmvpe" }
}
}
sample_rate 必须是 RVC 模型自己的采样率(看训练配置),不是输入音频的采样率。
f0_median_hz 用来定调:拿训练集 f0 中位当"你能舒服唱到哪"的基准。
加音色就往这个目录塞新的 json,cover_doctor 会认出来。
参数速查(cover_make)
| 参数 | 默认 | 说明 |
|---|---|---|
source |
必填 | URL / 本地路径 / 歌名关键词(走 ytsearch) |
voice |
_voices/ 里第一个音色 |
音色 id |
pitch |
自动 | 强制移调半音数 |
variants |
三档全出 | standard / forward / blend |
preset |
default | default / slow_ballad / fast_pop |
target_rms |
−13.0 | 母带目标 |
index_rate |
0.75 | 发飘往上调,贴原唱往下调 |
protect |
0.33 | 齿音糊往上调到 0.45 |
resume |
true | 沿用已有阶段(产物指纹没变才沿用) |
chunk_seconds |
120 | 换声分块秒数,4 GB 显卡别超过 150 |
force |
false | 闸门不过也导出 |
依赖边界
- 只要求 Applio env 里已有的
numpy / soundfile / librosa / scipy - 不引入 pyloudnorm / pedalboard / pyrubberband
- ffmpeg 用
<applioRoot>\ffmpeg.exe(那个版本的 amix 没有 normalize,所以混音一律走 Python 逐样本,不用 amix) - 移调用
asetrate + aresample + atempo,时长不变(实测 180.5013 → 180.4821)
已知边界
- 只有 2-stem UVR 分离,做不了主唱/和声分离
- 1050 Ti 4 GB 实测:4m36s 的歌分轨 62 秒、换声 223 秒;4m30s 的歌换声分 3 块共 267 秒,全程 299 秒
- 5m03s(平凡之路)分轨 62 秒、换声 4m56s,全程 483 秒
- 分离阶段没有分块机制,长歌的分轨显存峰值取决于 audio-separator 自身
- yt-dlp 走 YouTube,国内网络需要自己解决
实测台账
| 歌 | 时长 | 定调 | 全过程 | 结果 |
|---|---|---|---|---|
| 山水之间 许嵩 | 276.06 s(本地 48k 单声道) | 220.33 Hz → keep 0 | 374 s | 三档全过 |
| 夜空中最亮的星 逃跑计划 | 252.25 s(inmp3 128k) | 185.27 Hz → pitch −5 / both | 397 s | 三档全过 |
| 晴天 周杰伦 | 269.50 s(B 站 184k) | 238.89 Hz → keep 0 | 299 s | 三档全过 |
| 平凡之路 朴树 | 303.25 s(B 站 211k) | 142.04 Hz → keep 0 | 483 s | 三档全过 |
实测台账(1050 Ti 4 GB / 山水之间 276.06s / 48 kHz 单声道源)
| 阶段 | 耗时 |
|---|---|
| 取源 | 0.2 s |
| 分轨 | 62 s |
| 定调(pyin hop=1024) | 36 s |
| 换声 | 223 s |
| 对齐 | 5.6 s |
| 混音(3 档 + 干声) | 36 s |
| 验收 | 0.1 s |
| 导出 3 个 MP3 | 12 s |
| 合计 | 374 s |
成品 QC:标准版 peak −0.609 / RMS −13.032 / crest 12.42 / 削波 0;人声靠前版 −0.613 / −13.034 / 12.42;融合版 −0.609 / −13.030 / 12.42。三档全过闸门。
定调结论:原唱人声中位 220.33 Hz(A3),音色中位 172.89 Hz,semitone_median −4.2,但 high_overshoot 仅 1.57 半音(这首歌最高只摸到 G♯4),判 keep → pitch 0。
No comments yet. Be the first to write one.