直接拿整首混音去跑 MuScriptor,最容易卡住的地方不是模型本身,而是失败点被混在一起:环境依赖没装好、音频格式或采样率不被接受、导出链路写不出 MIDI、模型确实处理不了多声部编制——这四种情况在整首歌的输出里长得差不多。比较稳的做法是按复杂度分三级准备测试音频,先用单旋律干音跑通最小链路,再逐级加上和弦与合奏,最后才上整首混音。这样每一步失败都能落在具体环节上,而不是对着一个可疑的 MIDI 文件猜。
试跑顺序应当是单旋律 → 单和弦 → 小编制合奏 → 整首混音,每一级只增加一个复杂度变量,前一级没跑通就不进入下一级。单旋律那一步的验收标准是输出文件非空、能在 DAW 里解析出音符,而不是音准多好;合奏那一步主要看输出轨数、每轨音符数和空轨比例是否随复杂度出现明显退化。整首混音的输出默认不可直接用,需要按时间区间标成人工修谱任务。具体子命令和参数名以仓库实际接口为准,下面给的是可替换的骨架。
准备单旋律、单和弦、小编制合奏三段测试音频
三段音频都从自己熟悉、能听清内容的素材里裁,不建议用网上来源不明的片段,否则后面判断对错时没有参照。裁剪原则是每一级只比上一级多一个复杂度变量,其余条件全部对齐。
- 单旋律:清唱、单音吉他或单音管乐独奏,8~15 秒,只保留一条人声或一条旋律线。这一级的用途是验证链路,不追求难度。
- 单和弦:钢琴或吉他连续弹 4~8 个和弦,允许有少量转位和延音,但不要加鼓点。这一级相比单旋律只增加“同时发声”这一个变量。
- 小编制合奏:2~4 件乐器,建议包含一个节奏声部(鼓或贝斯)和一个和声声部,长度与前两段相近。这一级相比上一级只增加“多声部交织”这一个变量。
统一条件包括:采样率固定为 44.1kHz 或 48kHz 其中一种,三段用同一个值;声道数一致,建议都转成单声道或都保留立体声,不要混着来;时长尽量接近,便于对比时排除“跑得久所以出错”这种干扰;响度做一次归一化,避免某一段因为过轻被当成静音裁掉。建议在文件名里带上这些信息,例如 01_mono_44k_10s.wav、03_ensemble_44k_12s.wav,后面出问题时能立刻确认是不是素材本身的问题。
用最小脚本跑单旋律,确认 MIDI 能被正常打开
这一步的目的只有一个:证明环境、推理、导出这条链路是通的。因此参数要尽量朴素,不要开多轨、不要开高级后处理、不要换特殊精度。下面给的是通用骨架,子命令和参数名需要按仓库 README 或 `--help` 的实际输出替换。
# 占位命令:子命令名、参数名以仓库实际接口为准
muscriptor transcribe \
`--input` ./test/01_mono_44k_10s.wav \
`--output` ./out/01_mono_44k_10s.mid \
`--device` cpu \
`--log-level` debug
# 先看文件是不是真的写出来了
ls -l ./out/01_mono_44k_10s.mid
验证分三层做。第一层看文件:存在、大小明显大于空 MIDI 的字节数,重跑一次结果文件名不冲突。第二层看内容:用一个 MIDI 解析库读出轨数和音符数,能读出音符且音高大致落在人声或乐器的合理音域内,说明不是空壳文件。第三层放进 DAW:导入后能看到音符条、时间轴与音频对齐、播放时听感与原始干音大致对应。三层里任何一层不通过,就先停在这里查日志,不要继续往下跑和弦和合奏。
# 内容校验骨架,库名与接口以环境实际安装为准
import sys, pretty_midi
data = pretty_midi.PrettyMIDI(sys.argv[1])
total = sum(len(inst.notes) for inst in data.instruments)
print("tracks=", len(data.instruments), "notes=", total)
依次跑和弦与合奏,记录轨数与音符密度变化
从单旋律到和弦再到合奏,重点不是“能不能出结果”,而是模型在复杂度上升时以什么方式退化。建议每次跑完都填同一张表,统计口径固定为三项:输出轨数、每轨音符数、空轨比例。这些数字都可以从解析后的 MIDI 对象里直接读出来,不需要依赖日志里的推测值;如果导出环节会丢掉空轨,就以解析结果为准,并在表里注明该次输出是否发生过轨合并。
import sys, pretty_midi
p = pretty_midi.PrettyMIDI(sys.argv[1])
tracks = len(p.instruments)
counts = [len(i.notes) for i in p.instruments]
empty = sum(1 for c in counts if c == 0)
print("tracks=%d" % tracks)
print("notes_per_track=%s" % counts)
print("empty_ratio=%.2f" % (empty / tracks if tracks else 0))
print("empty_tracks=%d" % empty)
常见的退化形态包括:合奏输入下输出轨数没有相应增加,多个声部被挤进同一轨;音符密集地叠在相邻时间点,时值区分不出来;低音声部整段缺失,只在鼓点附近留下少量音符;或者反过来出现大量空轨。这些现象本身不构成结论,但对比三段音频的同一组指标,能看出模型是在“分轨”环节出问题还是在“音符识别”环节出问题,后面人工修谱时就能给对应的处理建议。
最后上整首混音,标出需要人工修的段落
整首混音跑出来的结果应当默认按“不可直接用”处理,把它当成一份待分工的修谱底稿。做法是在 DAW 里对照原曲听一遍输出 MIDI,按时间区间把问题段落切出来,而不是通篇打一个“效果一般”的印象分。建议用秒或小节号作为起止,字段至少包含:
start/end:问题区间,精确到秒或小节;issue_type:漏音、多音、音高错误、节奏偏移、轨分配错误、整段缺失;severity:阻塞(该段不可用)/需修(补删音符即可)/可接受(听感影响小);suggest_action:重新推理某一段、换单轨干音补录、手工拖音、直接删除;owner与status:便于多人分工和复查。
标注时以区间为单位而不是以单音符为单位,否则整首歌标下来工作量远超重做。一个区间内混有多种问题时,拆成多行并保留相同的起止时间,后面按 severity 排序就能决定先修哪几段。
把整套顺序写进可重跑的脚本参数
顺序固定下来之后,下一次换音频不应该重新摸索。把容易变的项外置到配置文件或环境变量,脚本只负责按 01、02、03、04 的顺序执行并在每级失败时退出。
# run_all.sh 骨架,命令与参数名以仓库实际接口为准
INPUT_DIR=${INPUT_DIR:-./test}
OUTPUT_DIR=${OUTPUT_DIR:-./out}
SAMPLE_RATE=${SAMPLE_RATE:-44100}
DEVICE=${DEVICE:-cpu}
LOG_LEVEL=${LOG_LEVEL:-info}
for f in "$INPUT_DIR"/*.wav; do
echo "== $f"
muscriptor transcribe \
`--input` "$f" \
`--output` "$OUTPUT_DIR/$(basename "${f%.wav}").mid" \
`--device` "$DEVICE" \
`--log-level` "$LOG_LEVEL" || exit 1
done
建议外置的参数项包括:输入输出目录、统一采样率、设备(cpu/gpu)、日志级别、单文件超时时间、模型名称或权重路径、以及仓库支持时的最大轨数与置信阈值。默认取值的来源应当是仓库的示例配置或 `--help` 里列出的默认项,不要凭印象编参数名;某个参数在当前版本不存在,就写进脚本注释而不是硬塞进命令行,否则下一次换版本会以“未知参数”直接失败,反而比手工摸索更难定位。