Skip to content

qa reads the score: a note's attack is not a click, the score's rest is not a dropout, each finding carries evidence - #115

Merged
ZLHad merged 5 commits into
mainfrom
claude/audio-qa-score
Oct 18, 2026
Merged

ZLHad merged 5 commits into
mainfrom
claude/audio-qa-score

Conversation

@ZLHad

@ZLHad ZLHad commented Oct 18, 2026

Copy link
Copy Markdown
Owner

What and why · 改了什么、为什么

bin/vh qa 的扫描把写好的音乐报成故障:音符的音头报成 click,两个拨弦音之间的空、一段有意放稀的段落报成掉音;报告给的依据又少。作者听不见,结果要么为了过检查去改音乐(加一条持续的底音),要么分不清哪处是真故障。这一版把检测器做准,不加规则,也不靠声明放过。

  • 扫描读谱子(节拍表里的音符表:声部写了 "note_map": true):
    • click 落在音符表里任何一个音的起点 ±--click-grace 以内,是这个音的音头:列在 "the score has a note here" 下(最严重的 5 处),不算 click。音符表只收了一部分声部也成立:一个起点就是一个起点。
    • 掉音里谱子一个音都没起,下一个音正好在它结尾进来(最后一个安静窗口前 0.15 s 到后 0.30 s),或者检查区间先结束了,而且没有比最后一个音更早写下、还在响的音:这是谱子的休止或两音之间的空,列在 "the score itself is quiet here" 下,不算掉音。原始结果都留着,只是分开列。
    • 这一条要求音符表就是整首谱子:bin/vh music 的节拍表现在多一个 unmapped(没写 note_map 的声部,用了 layers 时加 "layers";纹理声部不出音符,不列)。有东西没收进来,或者是没有这个字段的旧节拍表,掉音照报,后面写明原因。只在已经写 notes 的节拍表里加;其余节拍表一个字节不变。
    • 数字静音不管谱子怎么写都是失败,证据里照样写谱子在那里休止。
  • 每处的证据:每条没确认的 FAIL、WARN 都写读数和门槛(低于门槛多少 dB、超出多少)、第几小节第几拍、谱子在那里做什么(休止、两音之间、一个音一直写着、有音起了却没听到;click 写最近的音和在那里结束的音);给了 --stems 时写是哪条 stem(一段:各条 stem 在这段里比前 0.5 s 低多少;一个 click:哪条 stem 在那里也有一样大的跳变,都没有就是 "made in the mix")。--clips DIR 把每处前后 2 s 的混音写成 WAV,按时间命名(0012.340s-dropout.wav,最多 40 段),给人听。
  • 建议,不算失败:音符表的各声部合起来连续 16 小节以上每小节的起点完全一样(谱子里精确的重复,听不听得累由人定);混音报告 [5] 里同一类音效相对本地音乐床(re-bed)相差 15 dB 以上(一个写死的 dB 增益放在起伏的音乐床上:安静处太响、热闹处听不见)。完整运行最后一行写 "(advice above)"。不改混音。
  • 没有音符表时:计数和退出码和以前一样;bin/vh music 的节拍表没有音符表时多一行,说怎么加。
  • 文档:tools/audio/README.md(unmapped、扫描的谱子规则、证据、clips、建议)、qa 的帮助、music.py 的帮助(原来写"qa 不读 notes")、playbook/04 一行、playbook/02 表里两处小句(不改就和行为对不上)、CHANGELOG。

English summary. With the beat map's note map, the scan lists a click on a note's onset as that note's attack and a dropout where no note starts and the next one brings the sound back as the score's rest, both kept visible and not counted; the rest rule needs the map to be the whole score, which bin/vh music now records as "unmapped". Digital silence still fails. Every open finding carries its value against the threshold, bar:beat, what the score does there and, with --stems, which stem carries it; --clips DIR writes ±2 s excerpts. Two advice lines, never failures: 16+ identical bars of the note map's rhythm, and one SFX class spread 15 dB+ against the local bed. Without a note map, counts and exit codes are unchanged.

Review focus · 请重点看

  1. 掉音的判法够不够诚实:只看音符的起点和谱面时值,不知道乐器是拨的还是持续的。长时值的拨弦自己衰减下去,算"一个音还写着",照报(宁可多报)。纹理声部不进 unmapped(它们不出音符):纹理自己在两音之间掉下去,会被当成谱子的空。这一点写进了已知局限;要更严,可以把纹理也列进 unmapped,代价是带纹理的谱子永远用不上这条。
  2. 容差:下一个音在最后一个安静窗口前 0.15 s 到后 0.30 s(慢起音、onset_ms 留了余地);click 用现有的 --click-grace。
  3. music.py 的节拍表加了 unmapped:只在已经有 notes 时写。
  4. 两条建议的阈值(16 小节、15 dB)是给建议用的,不判失败。

How it was verified · 怎么验证的

  • tools/ci.sh --committed(VH_BASH=/bin/bash,shellcheck、pyflakes 按 CONTRIBUTING 的办法真的跑了):全部通过(合并 main 以后的提交);跳过 1 项(本机没装 HyperFrames 运行时)
  • 新的 CI 用例(合成音频、合成节拍表):落在音符起点上的 3 个 click 列成音头;谱子休止里的掉音不算;同一份音频不给节拍表时,掉音、click 和以前一样计数、一样退出 1,也不提谱子;音符表缺一个声部时掉音照报,证据行有门槛、小节、谱子、stems(music +0.0 dB),--clips 写出 0003.100s-dropout.wav;谱子休止里的数字静音照样失败;20 个一样的小节给建议、退出 0;bin/vh music 的 unmapped 列出没写 note_map 的声部,不列纹理。
  • 真渲的谱子:一段稀疏的古琴(bin/vh music,humanize 0.5)报了 4 处掉音,全是两音之间的空;读音符表一处不算,去掉音符表四处照报。同一首经 bin/vh mix … profile=promo stems= 混好、跑完整的 bin/vh qa … --stems:音乐 stem 的排除先起作用(4 处),剩下的 5 个 click 都写了 "in the music stem" 和最近的音。
  • bin/vh qa 的各模式(完整、scan、mix)真跑过;bed_advice 用构造的报告查过(同类 34 dB 的跨度出一行)。
  • 读量(tools/readpath.py,和分支起点比):quick 不变,standard +25,有声音的路径 +116。
  • main 在开发中途合了 Type docs' 禁止 sections split into floors and common misuses; general notes on facts, parallel agents, recordings; canvas-film's optional modules #112,已经把 main 合进分支(CHANGELOG 冲突,两条都保留),CI 跑在合并后的提交上。

Found, not fixed · 看到但没改

  • 上面那首真渲的谱子里,saw_pluck 声部在音符松键后约 0.1 s 处有 click(4.825、9.836、32.817 s,都在音乐 stem 里,比局部电平高 18–22 倍),像是它的余音被截断。没有单独验证。

Checklist · 自查

  • tools/ci.sh --committed passes · 通过
  • User-visible change noted in CHANGELOG.md → Unreleased · 已记进 CHANGELOG
  • Changed CLAUDE.md → ran bin/vh sync-agents · 没改 CLAUDE.md

ZLHad added 3 commits October 19, 2026 00:35
…is not a dropout, each finding carries evidence

The scan reported designed music as faults (note onsets as clicks, the gap
between two plucked notes and a sparse passage as dropouts) with little to
judge by. With the beat map's note map (music.py now also writes
"unmapped", what the map leaves out), a click on a note's onset is listed
as that note's attack, and a dropout where no note starts and the next one
brings the sound back is listed as the score's rest; both stay visible and
are not counted. Digital silence still fails. Every open finding gets its
value against the threshold, bar:beat, what the score does there and, with
--stems, which stem carries it; --clips DIR writes +-2 s excerpts. Advice
only: 16+ identical bars of the note map's rhythm, and one SFX class
spread 15 dB or more against the local bed in the mix report.
# Conflicts:
#	CHANGELOG.md
@ZLHad
ZLHad marked this pull request as ready for review October 18, 2026 17:05
@ZLHad
ZLHad enabled auto-merge (squash) October 18, 2026 17:05
ZLHad added 2 commits October 19, 2026 01:21
…ame-timeout 3)

GitHub's macOS runner took over 1 s on an ordinary frame, so --frame-timeout 1 stopped
the render at its second frame and kept 1 chunk, not 5.
@ZLHad
ZLHad merged commit 893cca3 into main Oct 18, 2026
2 checks passed
@ZLHad
ZLHad deleted the claude/audio-qa-score branch October 18, 2026 18:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant