DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

ALittleLov /

ALittleLov/dsh-soundcheck

Verified

让文本模型读懂一首歌:本地音乐分析插件,把音频拆成能量、速度、调性、密度,音频不出本机。

★ 3 Stars0 Forks0 IssuesN/A Community rating0 Confirmed installs
View on GitHub
READMESource: main@defcca9a

音频分析 · 现状与结论

给 DSH 加本地音乐理解能力的实验项目。 目标:让文本大模型能回答关于音乐的问题,不是做 FL 参数推荐器,不是播放器。 更新:2026-10-19

这份是开发说明,不是安装指南。 只想装上用,看同目录的《安装说明.md》。


⚡ 交接速览(新会话先看这段)

现在有什么:一个 DSH 插件,注册了三个工具 + 两个命令

工具 作用
audio_analyze 分析整首音频,返回文本或 JSON(可 brief 压缩)
audio_section 只分析一段 [start, end],回答"2:40 那里怎么回事"
audio_provision 装引擎(建 venv / 装依赖 / 下 YAMNet);已有 .provisioned 标记时直接返回"已装"
命令 作用
/audio-status 看引擎状态(Python 环境 / 脚本 / 模型 / 安装标记)
/audio-install 一键装引擎,返回装了什么、走了哪个镜像

安装策略:分析时不会偷偷装依赖(用户得先知道要下 470 MB)。引擎缺失时工具返回 ENGINE_NOT_INSTALLED,让用户跑 /audio-install,或者明确让助手代装。

插件位置:dsh-soundcheck-plugin\(已 dsh plugin add 到 desktop profile) **引擎位置**:开发机用项目根目录(venv + analyze.py);分发包用插件自带的 engine\ 分发包:dsh-soundcheck-0.5.1.zip(16.5 MB),由 pack.py 生成 GitHub:ALittleLov/dsh-soundcheck —— 旧账号 dalekotsvet 已被封(2026-10-20), 仓库已迁移过去,本地 remote 已切,旧下载链接全部失效

性能:4 分钟 mp3 整首约 11 秒(此前 66 秒)。两处白干的活已去掉: chroma 原本为调性和和弦各算一遍 CQT;YAMNet 原本把 mp3 再解码一遍。

输出长什么样(文本模式,默认只给判断):

[SCOPE] 只有片段分析时才出现:分析了哪一段
[判断]  一段人能直接读的结论:时长/速度层级/调性/母带/结构重心/最大反差/收尾
[依据]  判断背后的那几个数字(峰值、RMS、起音、调性、拍速)
[可信]  beat / 调性 / 根音 的适用性分数(0-1)
[听不到] 这类分析死也听不出来的东西

--detail(或工具参数 detail: true)才输出完整明细:

[FIT]  适用性自评 | [META][BPM][KEY][DYN] | [ACT] 活动度 | [FORM] 能量走势
[CHORD] 根音骨架 | [SEG] 段落边界 | [SRC] 声源 | [说人话] 旧版散文式总结

关键设计:默认先给结论,数字只在 [依据] 里。定性来自"别给用户甩一堆数据"这条反馈。 不给流派判断 —— 流派是听出来的,不是测出来的;要的话给"方向 + 置信",并标成推断。

当前瓶颈:说不出"音乐内容"(乐器、人声、语言、氛围)。光靠 DSP 补不上——见文末「9 个维度」,9 格里有 2 硬、5 半、2 空。 MusicNN(50 类)只覆盖西方音乐大类,不含中国民乐。

未发版的改动(2026-10-20 晚,等用户点头才发 0.5.3):

  • analyze.py 术语改英文: 抽空/砸回 → drop X dB / 回来; 直接收掉 → hard cut; 淡出 → fade out。起因:QQ 接入的用户把「抽空了一截」读成「最满」,方向弄反了 —— 事实证明形容词有歧义,数字不会
  • 判断里允许单个数字锚点(如「1:36 drop 9 dB」);自检同步放宽为「一行两个以上才报」
  • 这三处已在工作区生效,但 repo/engine 与 plugin/engine 尚未同步,发版时一起同步

当前待办(讨论完,未动手):

  1. 按需 stem 分离(4-stem 里吉他在 other,要 6-stem;CPU 慢需后台)
  2. 歌词接入(已验证:LRC 优先、ASR 仅兜底候选,见「歌词 ASR 验证」一节)
  3. 从 MOSS 借能力(当 Teacher 打标 → 训小模型;不把大模型塞进插件)
  4. 链接入口(取件与读件分离:下载归 ncatbot,插件仍只吃本地文件) 详见文末「下一步候选」与「9 个维度」。

踩过但放弃的路:

  • MOSS-Music 8B(18GB + CUDA-only)→ 用户否决
  • Essentia Discogs-EffNet(400 类风格,20MB)→ essentia.upf.edu 域名在本机不通,且官方 Windows Python 绑定不支持
  • GTZAN 训练(2001 年老数据、10 类、无国潮)
  • YAMNet 1024 维 embedding 做音频检索(2026-10-20 实测)→ 探针脚本见 _archive\probe_embed*.py。 用 8 首歌、音量归一化、中位数池化,按曲风分组算"同组−异组"区分度: YAMNet +0.106、MFCC 对照 +0.033。向量确有信息(为 MFCC 的 3 倍), 但最高相似对是 HAYA 蒙古民乐 ↔ 枪花硬摇滚(0.795),排序不符合音乐内容。 结论:向量有微弱信号,不足以支撑"这是马头琴/那是古筝"级别的判断; 要用出它的价值需要一批干净标注的参考样本,工量等于另起项目。不加入主流程。 同类线索(MusicNN 50 类标签、OpenL3 18MB 音乐嵌入)未跑,若将来有标注集再议。
  • Silero VAD v5 判断人声(2026-10-20 实测)→ 模型 _archive\silero_vad.onnx(2.2 MB),探针 _archive\probe_vad.py。 四首歌的人声块占比: HAYA papillon(有人声)12.0%、枪花(有人声)6.3%、 I Saw(纯器乐)7.1%、大提琴 cover(纯器乐)1.4%。 纯器乐比有人声的还高,阈值无法区分;最长连续人声段只有 1.44 秒,抓不住整段歌声。 结论: VAD 训练在『说话』上,歌声(长音/颤音/伴奏掩蔽)不在分布内 —— 既漏检又误检,不可用于音乐。 它偶尔高置信度误判(max 0.958),与 YAMNet 把唢呐报成 Choir 是同一个病根。

用户真正想要:Gemini 那种描述("马头琴、蒙古语、草原庆典")。已确认只能靠 Gemini API(约 ¥0.13/首),但用户不愿花钱/翻墙。

目前定下的方向:不追求"像 Gemini"的措辞,而是把"能测的"合成为判断、把"测不到的"明说。


目录结构

音频分析\
├── analyze.py          主分析脚本(结构/BPM/调性/根音候选/动态 + 可选 YAMNet)
├── yamnet_test.py      YAMNet 单独测试脚本(看时间线用)
├── yamnet.onnx         YAMNet 模型,16 MB
├── yamnet_class_map.csv 521 类标签,14 KB
├── dsh-soundcheck-plugin\   插件本体(源码,不含引擎)
│   ├── lib\index.js    宿主半侧,注册三个工具
│   └── cordis.patch.yml
├── README.发布.md      给用户看的说明(自述 + 安装合并版,打包时改名 README.md)
├── README.md           这份开发说明(不打包)
├── build\dsh-soundcheck\    打包暂存区(pack.py 生成,已脱敏)
├── pack.py             打包:拼装 + 脱敏扫描 + 压 zip
├── dsh-soundcheck-0.4.0.zip 分发包,13.6 MB(不含 Python 依赖)
├── test-plugin.mjs     插件端到端测试(假 ctx 收集工具,真跑每个工具)
├── test-activity.py    活动度指标的单独测试
├── venv\               Python 虚拟环境(librosa + onnxruntime),468 MB,不发
├── wheels\             离线 wheel 缓存,118 MB(重装用)
├── install-offline.sh  通用离线装包脚本(curl 下 wheel + pip --no-index)
├── download-wheels.sh  最初的 librosa 依赖批量下载
├── fix-deps.sh         最初的迭代补包
├── out.json            一份完整 JSON 样例
└── out.brief.json      一份 brief JSON 样例(约 530 B)

打包与分享:

  • 分发包 = 插件代码 + 引擎(analyze.py 54 KB + 模型 16 MB)= 13.6 MB
  • 用户机器的 Python 依赖(468 MB)由 audio_provision 在安装时下,不进包
  • 包里的引擎在 engine/,用户解压到哪都能自己找到;开发机上 profile 里用 config.root 指回工作区
  • README 分两份:README.md(这份,开发用,不打包)和 README.发布.md(用户版,打包时改名为包内 README.md)
  • 脱敏是硬门槛:pack.py 先复制到 build/,替换本机路径,再扫一遍禁用词 (用户名、本机工作目录、歌库文件名,词表就写在 pack.py 里);扫到就中止打包,不悄悄发出去
  • 改完 analyze.py 必须重跑 pack.py,否则分发包里还是旧的

发版流程(改完要更新 GitHub 时必须走)

推送前必须停下。 代码改完只在本地验证,把结果给用户看;得到明确同意后, 才允许 commit / tag / push / 建 Release。禁止自行判断「这个改动是对的」就直接推。 2026-10-20 定下的规矩:当时 0.4.2 抢先推送,被纠正过一次。

本地 commit 不等于 push,攒着发。 平时随便 git commit(纯本地,零网络流量); 到发版点再一次性 push(main + tag)+ 建 Release。一天 1–2 次 push 就够。 别滑回「改一下就推一下」——旧账号被封的教训之一就是节奏太密(新号 + 代理 IP + 高频操作, 是典型的自动化风险画像)。

不要改动完就 git push,那是"直接改文件"。 每次改动都按版本走:

  1. 本地验证 → 把结果给用户看 → 等明确同意(这步不能跳)
  2. 改完、本地验证通过(analyze.py 实跑一次看输出)
  3. 升版本号,两个文件必须一致:repo/dsh-soundcheck/package.json 与 dsh-plugin.json
    • 纯修正 → patch(0.4.0 → 0.4.1);用户可见的行为变更 → minor(0.5.0)
  4. 写 repo/dsh-soundcheck/CHANGELOG.md 对应小节,写清"改了什么、为什么"
  5. commit(沿用 conventional commits:fix: / feat: / docs: / chore:,正文中文)
  6. 打 tag:git tag -a vX.Y.Z -m "..."(历史上从来没过 tag,从现在起必须有)
  7. git push origin main && git push origin vX.Y.Z

配套动作:

  • 三份引擎副本要同步:analyze.py(根) → repo/dsh-soundcheck/engine/ → dsh-soundcheck-plugin/engine/。只改根目录那份,发布的包里还是旧的
  • 运行版插件的 package.json / dsh-plugin.json 版本号也要跟着升,否则装的和发的不一致
  • 发版后重跑 pack.py 产出新 zip

推送失败的常见原因(本机实测):github.com:443 连不上(网络层), 或凭据管理器弹不出窗(非交互 shell 没有 TTY)。两者都不是代码问题, 在普通终端里手动跑一次 git push 让它弹出登录窗即可。

用法

cd /d/DSH/音频分析
export PYTHONUTF8=1                     # 必须!否则中文/方块字符会崩

# 人类可读文本(默认:一段判断 + 依据 + 可信 + 听不到)
./venv/Scripts/python.exe analyze.py "<音乐目录>\music.flac"

# 完整测量明细(旧的仪表盘:FIT/能量曲线/和弦骨架/声源)
./venv/Scripts/python.exe analyze.py "<音乐目录>\music.flac" --detail

# 结构化 JSON(插件调用)
./venv/Scripts/python.exe analyze.py "<音乐目录>\music.flac" --json

# 压缩 JSON:只留结论级字段,约 1/25 的体积
./venv/Scripts/python.exe analyze.py "<音乐目录>\music.flac" --brief

# 只分析一段(--start/--end 接受秒或 m:ss)
./venv/Scripts/python.exe analyze.py "<音乐目录>\music.flac" --start 1:30 --end 2:10
./venv/Scripts/python.exe analyze.py "<音乐目录>\music.flac" --start 90 --end 130 --brief

# 分轨(可选组件;首次约 60–90 秒,之后读缓存)
./venv/Scripts/python.exe analyze.py "<音乐目录>\music.flac" --stems
./venv/Scripts/python.exe analyze.py "<音乐目录>\music.flac" --stems 6s

# 歌词听写(可选组件:先分离人声轨,再 faster-whisper;只输出候选段)
./venv/Scripts/python.exe analyze.py "<音乐目录>\music.flac" --asr
./venv/Scripts/python.exe analyze.py "<音乐目录>\music.flac" --asr small

# 只跑 YAMNet 看时间线
./venv/Scripts/python.exe yamnet_test.py "<音乐目录>\music.flac" --start 60 --dur 30

# 插件回归测试(用 DSH 自带的 node)
/node test-plugin.mjs

# 打包分发包(改过 analyze.py 之后必须重跑)
./venv/Scripts/python.exe pack.py

注意:

  • 中文路径必须硬编码或加引号传。经 bash 变量传递会编码坏掉。
  • 片段分析的时间戳是全曲绝对位置,不是片段内相对时间。

现在能做什么

能力 状态 说明
判断式输出 [判断] ✅ 默认形态 先给结论,数字只在 [依据];流派不猜
结构 / 能量走势 ✅ 可靠 16 秒块 RMS,0 dBFS 为满刻度
BPM ✅ 可靠 含倍频校验 + 低频第二意见(但校验不出"检测器本身锁在双倍速")
调性 ✅ 可用 Krumhansl 模板 + Top3 候选 + 歧义分类
适用性自评 [FIT] ✅ 最有价值的一块 在 --detail 与 JSON 里,决定哪些结论能信
声源 / 乐器(YAMNet) ✅ 部分可用 16 MB,1000× 实时,CPU;情绪/风格标签已剔除
响度 / 削波 ✅ 可靠 真峰值 + 整体 RMS + 贴顶点计数
和弦 ⚠ 只到根音级 根音候选骨架;性质/七度分不清。见结论 4
片段分析 ✅ 可用 audio_section,时间戳为全曲绝对位置
分轨(人声检测) ✅ 新增 0.6.0 --stems 4s/6s:各轨能量占比 + 人声判定;组件可选(约 150 MB),缓存后秒回
歌词听写 ⚠ 候选级 --asr:4s 人声轨 + Whisper;只输出「较确定」段,不是逐字准确;见「歌词 ASR 验证」
流派 / 风格 ❌ 听不出来;工具会明说"风格判断不了",不编
自然语言描述 ❌ 需要大模型(已决定不做)

四个最重要的结论

1. [FIT] 适用性自评必须保留

它决定"这些结论能信多少"。设计它的时候,自己被它抓出了 10 个 bug。

每个维度都要区分两种失败:

失败类型 表现 例子
体系外(模板不适用) 绝对可信度低 维吾尔音乐 vs 西方调性模板(key_r 0.55)
体系内歧义 绝对可信度高、差距小 属调混淆(0.91 vs 0.83)

只看后者,就会把"模板不适用"误判成"可用但有歧义" —— 这是最危险的错。

调性的判据:

  • key_r(绝对相关度):< 0.65 判定"模板不适用"
  • gap(最佳与次佳差距):≥ 0.15 判定"调性明确"

四首实测:0.913 / 0.900 / 0.550 / 0.894 —— 三首西方体系 + 一首维吾尔,分得很干净。

2. YAMNet 的边界很清楚

能给 不能给
✅ 乐器/声源(Percussion / Singing / Drum) ❌ 情绪(Sad/Tender 是猜的)
✅ 地域线索(Music of Asia / Bollywood) ❌ 风格(Christmas/Christian 是噪声)
✅ 相对比较(两首像不像) ❌ 绝对判断(分数只有 0.01–0.05)

原因:AudioSet 训练数据偏西方流行,没有"抒情/慢板"这类类别,模型找不到就跳到 Christmas/Christian 这种特征相近的类。

用时必须带上这句警告,否则会把"圣诞音乐"写进报告。

2026-10-19 补充:中文乐器这层是噪声。 实测把「唢呐」报进了两首根本没有唢呐的曲子 (一首古筝领奏的器乐曲、一首民乐曲),「二胡」在各首之间到处排第一。 它报的不是乐器,是"中国民乐"的刻板标签。所以判断里已不再出现配器行,原始分数只在 --detail 存档。

同一天还试了 CLAP(零样本,文本比音频):8 首里乐器 top-1 只对 4 个,全是西方通用类 (钢琴 / 电吉他 / 电子鼓);古筝、琵琶、马头琴一个都没进前四;风格则「流行」几乎霸榜。 第一版用中文提示词是无效的(CLAP 文本端是英文训练的),改英文后西方乐器变好、中文乐器仍然为零。 结论:零样本这条路对中文乐器不通,已删除实验环境(2.7 GB)。这也说明早先"Zither→古筝"命中是运气。

2026-10-21 补充:非西方打击乐同样是盲区。 《两只猫头鹰》(代青塔娜,纯吟唱剪辑版)实测, 人耳明确听出的沙锤与鼓,YAMNet 全量 521 类里最高仅 0.09(Rattle)与 0.02(Drum 系),0.10 阈值下零命中; 同一首只有吉他与钵/锣成立(吉他 0.52,集中在 0:01–0:26;钵 0:14、锣 3:08、钵 3:20,均为点状)。 "模型没标签"不等于"音频里没有",这类判断只能靠测量,不能靠模型默不作声。

替代做法(实测可用,暂不入插件):librosa STFT 分频带(6–12k / 2.5–6k / 0.8–2.5k / 250–800 / 80–250 / 30–80 Hz), 每带做包络差分 + scipy.signal.find_peaks 取打击点,统计间隔中位数与变异系数 cv。 判据:cv < 0.5 视为该带节律规律,可判为"节拍器层"。该曲 6–12k 带 cv=0.42、间隔 0.44s、 主周期 0.89s(=67 拍),与 BPM 66 的八分音符吻合 → 沙锤;低频 30–250 Hz 占能量 35%、 打击点密但 cv>1.1 → 打击铺底,不是节拍器。参考脚本 _archive/band_rhythm_probe.py。

3. BPM 会半速误判,而且旧的自校验抓不到

实证:一首自作曲,beat tracker 报 74.9,真实约 150。

为什么原来的"倍频校验"失效:beat_track 的 tempo 和"节拍间隔反推"来自同一次错误判断——它锁在"每两拍算一拍",两个数字当然一致。等于自己给自己作证。

修法:用独立信号做第二意见 —— 30–200Hz 频段 onset 间隔的中位数。同一首歌这样算出 156.6,差 4.7%,并在文本里主动报警。

局限:kick 和 bass 抢同一频段,纯低频检测本身也有天花板(可能把 bass 音符当成 kick)。这是"第二意见",不是"正确答案"。

补充实测(一首钢琴曲):beat tracker 98.7,低频第二意见 216.3。用 onset 自相关独立看, 峰在 194–216 一带(约 2×98.7)—— 说明两个拍层级都真有能量,不是谁算错了。所以措辞已经从 "很可能真速是后者"改成"两个拍层级都有能量,别把其中一个当唯一答案"。钢琴/无鼓曲目里, 低音音符会伪装成底鼓,这条第二意见尤其容易误报。

2026-10-21 补充:倍频错误是双向的,而且合成信号测不出来。

  • 合成 click 轨(80/120/160)测得很准(误差 ≤2 拍)—— 但真实音乐里它锁错倍频很常见
  • November Rain 官方约 80,检测器报 156.6(约两倍),低频第二意见又给 304
  • 用户反馈:一首摇滚报 80,听感 160(约一半)
  • 所以「报 156 实际 80」和「报 80 实际 160」都会发生 —— 方向相反,同一病根

修法(已落地):新增 bpm_layers(),永远同时给两个倍频层级,不再只报一个数:

  • 判断里:拍速是两层:157 和 78,两个都可能,取决于你怎么数拍(有分歧时另写「低频层和瞬态层各说各的」)
  • [依据] 里:157 / 78 拍(两层)
  • JSON:bpm_layers 给两层原始值,不标注哪个是「真速」

关键补充:《月之暗面》(网易云权威值 69)检测 139.7(约两倍)——前后半段一致、 三个信号自洽,说明连「节奏稳定的歌」也会锁双倍。所以不再推荐哪层是主答案: 推荐错了比不推荐更糟,只把两层摆出来,由听的人按自己的数拍方式判断。

Beat This 集成(2026-10-21 晚,0.6.6 候选):BPM 检测改为可选组件—— 模型 dsh-soundcheck-data/models/beat_this/beat-this-final0.onnx + frontend.json (82 MB,MIT,ONNX,纯 onnxruntime,不需要 torch)。有模型用 Beat This,没有自动回退 librosa。

歌 librosa Beat This 真值
月之暗面 139.7 69.8 69
November Rain 156.6 78.9 ~80
蓝色骏马 152.0 76.9 —

12 首对比:final0(82 MB)与 small0(导出 9.9 MB)有 10 首一致;small0 在 papillon 上翻倍、 final0 在 23 钢琴上翻倍——各有弱点,故选 final0(摇滚/电子更常见,且 BPM 是核心指标)。

实现:detect_tempo() 先试 Beat This(30 秒分块——Transformer 的 attention 对长序列 会吃大量内存),失败或模型缺失回退 librosa;[依据] 标「(Beat This)」,JSON 加 bpm_source。 small0 的导出脚本在 _archive/export_beat_this_small0.py(torch→ONNX,可复用)。

4. 和弦:不是"识别率低",是"性质根本分辨不出来"

先纠正一个旧数字。 以前写的"未定率 71–82%"是错的 —— 那个字段算的是 1 - 变化点数 / 块数,即**"和弦没变的比例"**,不是识别失败率。标签撒谎。

真正的测量(一首钢琴曲,126 个 2 秒块,84 个和弦模板):

层面 "最佳 − 次佳"差距的中位数 判读
具体和弦(D#maj7 vs D# …) 0.026 和噪声分不开
折叠成「根音 + 大三/小三族」 0.036 仍然很紧
只比根音(12 个) 0.051 79% 的块有明显胜出者

结论:混音上的 chroma 只够猜根音,不够定和弦性质。七度、挂四、转位 在混音里被所有乐器互相掩蔽,再好的匹配器也只能在噪声里挑一个。

所以降级为候选模式(已实现):

  • 报根音骨架:0:00 D#(M) | 0:21.8 C(m) | …,M/m 表示大三/小三
  • 性质分不清就写 (?);根音也定不下来就加 ?;被过滤掉的抖动段标 ⟨12s未定⟩
  • 另报候选词表(各根音占多大比例),并明说"这是候选,不是和弦进行"
  • unresolved_ratio 现在是真正的未定比例(根音差距 < 0.025 的块占比)
  • [FIT] chords 也改为按根音差距算 —— 它评估的是"根音骨架能信多少",不再是"和弦名对不对"

四首实测的根音未定率:钢琴曲 26% / 蒙古族乐队曲 38% / 自作曲 45%。 非西方与重混响素材明显更差,这正是 [FIT] 该反映的。

歌词 ASR 验证(2026-10-21)

问题:没有 LRC 的歌,能不能靠 Whisper 补歌词?

方法:_archive/probe_asr.py —— 4s 分轨取 vocals 轨 → faster-whisper(int8, CPU)转录 → 与 LRC 按字算 CER + 逐句对照。样本《蓝色骏马》(普通话)、《万物生(梵)》(梵语,作反例)。

结果(《蓝色骏马》,参考 141 字):

模型 体积 段数 输出字数 按字 CER 表现
tiny ~75 MB 17 120 79.4% 只对副歌「好久不见」
base ~145 MB 67 324 200% 幻觉多,编出英文段
small ~461 MB 15 820 548% 前段最好,英语段循环失控
small_rep — 29 313 199% 抑制重复后长度减半,循环仍在

CER 被「输出字数爆炸」污染,看逐句更公平。small 在主歌前段确实能读出来: 「霞上山坡→向上山坡」「远远带来一只蓝色骏马→远远带来一支蓝色军马」 「好久不见 难得一见→好久不见 难得遇见」。但 131 秒后三档全崩。

梵语样本更说明问题:三档全部幻觉,且随模型变大「编得越来越像歌词」—— tiny 出「咛咢咢咢咢」,base 出英文 ounce my shadow,small 出「Zither Harp」「字幕 by 索兰娅」, small_rep 直接编成「我愛你,你愛我」的情歌。模型不会说「我听不懂」。

结论:

  • 插件内最多用 base(145 MB);small(461 MB)比 4s+6s 分轨模型总和(288 MB)还大 1.6 倍, 效果仍不可靠,不划算
  • ASR 只能作为「候选 + 必须标注机器听写」,不能写进判断
  • 参数调优(no_repeat_ngram_size / repetition_penalty / temperature=0)只治输出长度, 治不了「编不编」——这不是参数问题,是模型能力边界
  • 因此歌词接入顺序不变:先 LRC(35% 覆盖),ASR 只作兜底

产物:_archive/probe_asr.py、_archive/asr_probe_report.md、_archive/asr_out/(两首 vocals wav)。

实现状态(2026-10-21 下午):analyze.py --asr [tiny|base|small] 已加,默认不跑。 输出 [听写] 段:只列「较确定」的候选段;疑幻觉段连内容都不给(JSON 里 text=null)。 梵语样本实测输出「本曲没有可用的歌词段」,是正确拒绝。 注意:候选段里仍会有错字——实测《蓝色骏马》11 段候选约一半是错的(「漫個意見」实为「难得一见」)。 所以措辞是「候选」不是「可信」;最终该不该信由调用它的模型判断——这是设计,不是缺陷。

音译规则(2026-10-21):非中文人声(梵语、藏语、自语、吟唱)不得翻成中文词义—— Whisper 强制中文会产出「我愛你」式幻觉;不强制则换成语种幻觉(《蓝色骏马》整首被听成 英语「How do you feel」)。两条路都只给词、不给音。所以:

  • 有 LRC(含梵文/藏文):转写罗马音或音译,标来源
  • 无 LRC 且听不出词:只报时间,不给文本;转述时用音译近似(shai-lu-way、om banza…), 并说明「这是音,不是义」
  • 永远不给中文翻译,除非确认是中文唱的词

「没有可用歌词段」≠ 纯器乐:先看 [分轨] 的人声占比;有人声却读不出,就说 「有人声但词读不出,可能是外语或吟唱」,不硬编。

真人 vs AI 人声(2026-10-21 实测):

样本 候选段 表现
《起初说》(真人,有完整 LRC) 18 段里 2 段 音对字错:「我诞于尘土」→「我的夜长途」、「奔向尘土」→「半想传途」;结构能看出是「…归于尘土」,字全错
《蓝色骏马》(真人) 37 段里 11 段 副歌能对(「好久不见」),主体错
AI 生成人声 7 段全废 连音都拼不成句子(「梦里欧霞」「伊莉茨」「导致山蕉的青鳳」)

结论:真人歌上 ASR 的价值是「听出音、让你去认字」,不是直接给歌词; AI 生成人声基本读不出——合成音色、不自然的辅音与尾音不在 Whisper 训练分布里, 再加上编曲覆盖,连候选段都很少。遇到「有人声但读不出」,先问是不是 AI 生成的人声。

踩过的坑(按教训价值排序)

# 坑 教训
1 [FIT] 说 tonality 0.05,同一份输出 [KEY] 说 0.91 自相矛盾的输出是最强的 bug 探测器
2 "块平均 RMS"冒充"峰值" 标签撒谎比数字错更危险
3 tonality 用熵,12 个 bin 永远算出低值 指标要有区分力,不能算出来都一样
4 把"属调混淆"说成"无调性" 体系内的语言不能用来描述体系外的问题
5 "疑似削波"用峰值判断 削波要看贴顶采样点数量,不是峰值
6 key_ambiguity 只看关系不看差距 0.89 的 D minor 被说成"调性不明确"
7 pip 网络请求被拦截(疑似杀软 SSL 扫描) 用 curl 下 wheel + pip --no-index
8 脚本选到预发布版(protobuf rc) pip 默认不装 pre-release,要过滤
9 只认 cp312-abi3 没认 cp310-abi3 abi3 要泛化匹配
10 中文路径经 bash 变量传参编码坏掉 硬编码路径
11 chord_unresolved_ratio 用"和弦没变的比例"冒充未定率 指标名字和算式必须对得上,否则整份结论都建在错数字上
12 fmt_time 把 239.999 秒打成 3:60.0 先四舍五入到目标精度再拆分,别对原始浮点取模
13 同一份音频被解码两遍、CQT 算两遍(4 分钟的歌 66 秒) 先量耗时再谈优化;66s → 11s 只改了两处重复计算
14 [FIT] tonality 0.41 不可用 旁边写"中心基本明确" 前缀不要替调用方下结论;不可用 后面必须跟真正的理由
15 54 秒片段测速 65.4,全曲却是 98.7 短片段会锁到别的拍层级;片段 BPM 不等于全曲 BPM,输出里必须警告

其中 8 个是被真实样本逼出来的,不是设计时想到的。(11–15 是本轮新暴露的)

关键决策记录

MOSS-Music 8B 已决定不做。 理由:

  • 模型 ~18 GB + ROCm + Python 3.12,无法作为可分享插件的依赖
  • 定位应降为"可选增强",而不是核心
  • 用户判断:插件不能要求别人下十几 GB

当前路线:轻量(librosa + YAMNet),总依赖 < 1 GB,CPU 可跑。

外部建议:GPT 的三轮意见(2026-10-18/19)

用户当信使,把项目现状转给 GPT 咨询。这里只记结论和判断,不抄原文。

第一轮(架构)

  • 双分支:「音色复现」和「音乐理解」要的证据不同,别混一条流水线
  • 测量 / 推断 / 建议 必须分开:「4.2kHz 有能量」是测量;「可能用了失真」是推断;「Mix 调 35%」是建议
  • 不要先分离再决定分析什么 —— 分离伪影会污染音色判断
  • 削波会伪装成失真(现场录音尤其)
  • 误差传播链:分离伪影 → 错音符 → 错和弦 → LLM 用流畅语言解释错误事实

采纳:测量/推断分离(体现在 [FIT] 和"说人话"的分工)、误差传播的警惕 未采纳:双分支(还没做音色分支)

第二轮(和弦 / BPM / 动态 / 自评)

  • 和弦:建议试 BTC(预训练,12.2MB 权重);Chordino 作传统基线。未做(需要 PyTorch,且要导出 ONNX)
  • 三态输出:可靠识别 / 候选歧义 / 证据不足;N/无和弦 和 unknown 必须分开
  • BPM:建议 DeepRhythm —— 未采纳,许可证 AGPL-3.0(传染性),且要 PyTorch
  • 编曲动态 vs 响度动态:必须拆成两组指标 —— 已采纳并实现([ACT] 段)
  • 四字段自评:applicability / confidence / ambiguity / stability —— 部分采纳(目前只有单一 [FIT] 分数)
  • 类别缺失 ≠ 低置信度 —— 已作为原则采纳(YAMNet 没有竹笛类,不等于没有竹笛)

第三轮(风格分类)

  • Essentia Discogs-EffNet:400 种风格,权重约 20MB,CPU 可跑 —— 但
    • 官方 Windows Python 绑定不支持
    • essentia.upf.edu 在本机完全不可达(DNS 失败)
    • 分类头是 .pb(TensorFlow),要转 ONNX
    • 预处理必须严格复现,而参考实现装不上 → 无法验证
    • 许可证 CC BY-NC-SA 4.0(非商业 + 相同方式共享,边界模糊)
    • → 判定:不做
  • MAEST:345MB,模型和分类头都有 ONNX(不用转)—— 太大,不做
  • 轻量 HuBERT:24MB,Apache 2.0(许可干净),但只有 GTZAN 10 类 —— 可用但覆盖窄
  • CLAP 体积更正:我之前说 700MB,实际音乐专用检查点约 2.35GB
  • CLAP 零样本能力:GTZAN 上 Top-1 约 71%(项目自测),但没有"蒙古迪斯科"这类细分的基准

GPT 的最终判断(我认同)

只靠人工设计的 BPM / 调性 / 起音密度 / 动态 / 频谱统计,不能可靠还原文化语义。 允许用预训练模型,可以逼近其中相当一部分,但取决于模型学到了什么。


讨论中确立的六条原则

  1. 先给结论,数据按需 —— 默认输出是判断,不是仪表盘;数字放 [依据],明细要 --detail
  2. 敢下判断 —— 用肯定句给最可能的答案,再标置信(高/中/低), 只指出"哪一层不确定";不要每句都留退路,也不要用数据代替判断。 推断层鼓励大胆猜,但要给"反证条件"(如果是 X,你会听到 Y),让用户听一遍就能否掉
  3. 对用户只谈音乐,不谈管线 —— 不提 [判断]/[FIT]、不提 YAMNet、不报置信小数; 说"人声像在垫底",不说 Choir 0.021
  4. 测量与推断分离 —— 文本输出里,哪些是量的、哪些是猜的,必须能分辨
  5. 不确定性显式化 —— 低分项要标(?)、空白要标(⟨51s未定⟩)、不适用的要判"不可用"
  6. 类别缺失 ≠ 没有 —— 模型没有"竹笛"这个类,不等于这段没有竹笛

用户明确否决的方向

方向 否决理由
MOSS-Music 8B 本地跑 18GB,无法作为分享插件的依赖
Gemini / Qwen API 要钱、要梯子、麻烦
训练专用分类器(GTZAN / 民乐) 数据老、类别不全、工作量大
FL Studio 参数推荐 用户不需要
频段节律拆解 / YAMNet 521 类全量并进插件 使用频率低;插件保持轻量;需要时走临时脚本,方法见「YAMNet 的边界」一节(2026-10-21 拍板)

校准样本(有标准答案的)

样本 人耳确认 工具给的 结果
一首古筝领奏的器乐曲 古筝、无人声、民族/东方融合器乐、偏配乐/国风电子 筝类拨弦;五声化根音 D–E–F#;慢热加码 + 4:32 塌 + 4:48 硬收尾;方向偏民族/配乐 筝 ✅ 调式 ✅ 结构 ✅ 方向 ✅ 人声 ❌(误报 Choir/Singing) 配器 ❌(CLAP 报了唢呐,实际没有)
一首民乐曲 民乐,没有唢呐 声源标签里出现唢呐 配器 ❌
一首蒙古族乐队曲 确实有马头琴 CLAP 给的是 人声 / 弦乐 / 钢琴,马头琴未进前四 配器 ❌(真的有的东西没认出来)
HAYA《papillon (Live)》 马头琴、冬不拉、钢琴/pad、电吉他、鼓,黛青塔娜女声嘶吼 MusicNN: rock 0.24 / guitar 0.20 / female 0.16 风格 ✅(摇滚底+吉他+女声全中)、配器 ❌(马头琴/冬不拉/钢琴一个都没有 —— 标签集里没有)
《The Rebel Path (cello cover.)》 开头大提琴,主体是 techno 编曲 MusicNN: techno 0.40 ✅ 对(曾误判为"模型发疯",实为人耳先入为主)
《I Saw》 钢琴独奏 MusicNN: piano 0.94 / slow 0.37 / solo 0.32 ✅ 准得吓人
Guns N' Roses《November Rain》 摇滚乐队,吉他、男声 MusicNN: rock 0.23 / guitar 0.22 ✅ 对

MusicNN 四首校准结论(2026-10-20):风格/大类判断 4/4 全对; 具体乐器只有标签集覆盖的能对(钢琴、电吉他),中国民乐一律无解。 所以它接在 [标签] 行,措辞是「模型标签(非识别)」,不进 [判断]。

三条教训:

  • 具体乐器线索即使只有 0.012 也可能对(Zither→古筝);而 Choir/Singing 这类容易被持续音、混响垫出来,不能当"有人声"。
  • 工具写"线索偏 X,只能当方向"时,别在转述里升级成"像在垫底" —— 升级是转述者干的,不是工具的错。
  • 别拿未验证的印象当"已知答案":蒙古族乐队那条写进测试表时没确认过,后来才发现预期是对的、模型是错的。

下一步候选

  1. 插件骨架 ✅ 已做(三个工具)
  2. 压缩 JSON --brief ✅ 已做(约 530 B,约 130 token)
  3. 局部分析 audio_section(path, start, end) ✅ 已做
  4. 和弦降级为候选 ✅ 已做(根音骨架)
  5. 音色分支(GPT 第一轮建议)—— 现在所有结论来自混音;要看"失真/削波/高频亮度"得单开一条
  6. [FIT] 四字段化 —— applicability / confidence / ambiguity / stability 拆开(现在挤在一个分数里)
  7. 缓存 —— 同一文件连问两段会重复解码;按 文件+修改时间 缓存 chroma/能量曲线
  8. UI 卡片 —— 现在只有文本;DSH client 半侧能画能量条/骨架,尚未做
  9. 按需 stem 分离 ✅ 已做(0.6.0)—— --stems 4s/6s,基于 demucs-onnx(纯 onnxruntime, 不依赖 PyTorch)。实测 4-stem:纯器乐 vocals 约 1e-6、有人声约 25–38%,判得很干净。 模型可选安装(4s 约 150 MB fp16,6s 再加 150 MB),结果按文件缓存、重复问秒回。 仍是同步等待(短歌 60–90 秒);长歌超时提示分段。
  10. 歌词接入的可行性验证 ✅ 2026-10-21 已验(见「歌词 ASR 验证」一节)—— 结论:LRC 优先(23/66 = 35% 覆盖),ASR 只作兜底候选且必须标「机器听写」; tiny/base 不可靠,small 体积不划算,暂不接入插件。 LRC 是人工做的,版本可能对不上,对不上时要能发现并报出来,不能硬解释
  11. 从 MOSS 借东西 —— 蒸馏不可行(18 GB + CUDA,教师跑不起来)。可借:它的标签体系 (它认为音乐该有哪些属性)、它的评测方法/基准、预处理细节。借来的是“该测什么”的知识, 不是模型能力本身。先读论文/文档,不跑模型

已经定过的原则(新会话别重开)

2026-10-20 讨论的沉淀,每一条都是踩坑换来的:

  1. 大模型是 Teacher,不是产品 —— MOSS / SongFormer 只在开发侧跑(打标、验证), 用户永远不碰;进插件的必须是蒸馏/量化后的小模型(<100 MB)。 一度想把 MOSS 跑进插件,被用户纠正:那已经不是插件了,是另一个项目
  2. 搜索与先验补不了“听” —— 联网查歌手、用模型知识猜风格,都不能替代音频测量。 它只能补“这是谁”,补不了“这首听起来像什么”。 反例:知道是“cello cover”就断定它该报大提琴,结果中段是鼓 + 电音
  3. 先盲测后查证 —— 先只用音频出结论,再去查资料;两栏输出,永不合并
  4. 术语用英文 —— drop / breakdown / fade out / hard cut,不硬翻成“抽空/砸回”
  5. 判断里允许单个数字锚点 —— 防误读;但一行两个以上就变成甩数据
  6. 取件与读件分离 —— 下载音频(可能联网)和本地分析是两件事。 插件只负责“读”:“纯本地”约束的是分析,不是音频来源
  7. 测量与资料永不合并 —— 输出里测的、模型标的、查到的、推的,必须能分清

音乐理解的 9 个维度(借用 MOSS-Music 的 caption 评测框架)

拿它当尺子量自己,空缺一目了然。这 9 维来自 MOSS-Music 评测 caption 时用的维度集合:

维度 我们的状态
速度 / 节奏感 ✅ 硬(BPM + 双层检测)
结构与段落变化 ✅ 硬(但是能量层面,不是音乐学上的 verse/chorus)
配器 / 音色 ⚠️ MusicNN,只认标签集内的;中文民乐全无
人声相关 ⚠️ 弱,分不清唱还是念
旋律 / 和声 ⚠️ 只有根音候选
制作 / 声学质感 ⚠️ 响度、削波、贴顶平台,仅此
风格 / 流派 ⚠️ MusicNN 50 类,风格大类准
情绪 / 氛围 ❌ 空
场景 / 用途 / 语义联想 ❌ 空

9 个里 2 个硬、5 个半、2 个空。 这不是问题清单,是地图 —— 下一步该补哪格,一目了然。

已知限制(用之前必须知道)

  • 片段 BPM 不可当全曲速度:54 秒片段在一首钢琴曲上报 65.4,全曲是 98.7。输出会警告,但读的人仍可能忽略
  • 片段统计更抖:同样的 2 秒块,整首有 126 块、片段只有 28 块,[FIT] 分数会波动
  • 声源标签只到"像什么":YAMNet 给的是 AudioSet 类别,不是乐器识别;"Bowed string instrument"不等于"马头琴"。 它能给方向(像拨弦类/偏东方),但给不了名字;中文乐器误报率高(实测把唢呐报进没有唢呐的曲子), 所以判断里不写配器行,只在对话里按"推断"说
  • 没有旋律、歌词、语言:音符级的内容是本地信号分析的天花板,不是 bug
  • 乐器/风格没有可靠的分类方案:YAMNet(521 类事件)和 CLAP(零样本)都试过,中文乐器都是零; 参考库(音频对音频)是唯一没试过的路,但需要干净的乐器样本,且混音干扰仍在。 在找到可靠方案前,这两项只给"方向 + 置信",不给结论
—/ 5

No ratings yet

Verified DSH bundle

Commit defcca9a662a

Community comments

No comments yet. Be the first to write one.

DSH HUB

A community index for DSH plugins. Not an official GitHub or DeepSeek AI product.

CommunityResourcesAPIAbout