Rendered from the source repository. Headings, examples, code, tables, links, and referenced images are preserved.
Rap Beat Align(说唱人声对伴奏节奏对齐)
经 v4→v13 十三轮实战迭代打磨的完整方法论。一句话总纲:切短语(标点粒度)→ 校速度(全局一次)→ 找真拍(直方图裁决相位)→ 密集落点(整数拍步进)→ 样本级搬运 → 读回复测 → click 验收。
在 rap-clone-workflow 中的角色:S5 对拍站。 输入 = 04_tts/dry_vocal.wav + 02_analysis/dna_card.json(BEATPHASE 每首重测、GAPBEATS = 卡内 gapbeats、首句时刻 = 卡内 firstphrasebeat)+ `03lyrics/lyricsfinal.json`(短语表);产出写入 `05align/`。独立使用时按下方"输入"节自备素材即可。
输入
- 人声:AI 生成干声,或混音文件经 demucs 分离的人声轨;
- 伴奏:wav/mp3;
- 歌词文本:提供标点短语数 N 和字数模式(实例:36 短语 = [3字,3字,7字]×12);
- 可选:用户手动对齐的参考版(密度金标准,但相位不可继承,见铁律 3)。
两阶段流水线
Stage A:精确测速 + 全局校正(scripts/measure_tempo.py + scripts/global_tempo_fix.py) Stage B:标点短语级对齐(scripts/align_phrases.py 一步到位:切分→落点→搬运→自验证→混音→click→报告)
Stage A 步骤
- 角色判定:人声 300-4kHz 无低频/高频截断;伴奏全频带低频贯穿。混音文件先分离:
python -m demucs --two-stems=vocals -n htdemucs -o demucs_out <file>。 - 精确测速(双方):
measure_tempo.py做 onset 级自由周期 lattice 拟合(扫描周期+圆形方差找相位)+ 迭代回归精化,残差 med <10ms 才算锁定。绝不信 librosa beat_track 的 tempo 报告值——swing 节奏陷阱:实例伴奏真值 120.01 BPM,它报 117.45。 - 失配判定:双方速度差 >0.3% 即失配。实例:人声 131.23 vs 伴奏 120.01 BPM(快 9.35%)——此时切句/挪句头/模式重排全部无效(20 秒处漂出 ~2 秒),唯一正解 = 全局一次性校正。
- 全局校正:
global_tempo_fix.py调 rubberband 均匀拉伸(音高不变,保留所有字间相对关系)。向用户说明:这与被否掉的"逐字破坏性拉伸"本质不同;音色轻微变软,不可接受则按伴奏 BPM 重新生成人声。
Stage B 步骤(align_phrases.py 内置)
- 拍序列:
beat_track(units='time')→ 整体平移校准到 BEAT_PHASE → 尾部按拍周期外推补齐 → 自检 onset 命中率(随机相位基线 ~12%,实测 46% 合格)。 - BEAT_PHASE 裁决(每首必重测,禁止沿用旧值):
onset_detect(hop=128, n_fft=1024)事件时间 mod 拍周期 → 16-bin 直方图 → 事件聚类即真相位。实例:0.188s(拍头)+0.438s(反拍)双峰集中 139/153 事件,其余相位为空。用户参考版的手摆相位有 70-80ms 系统性提前,绝不能当金标准继承——沿袭它导致人声永远在拍前冲。beat_track 相位自带 ~30ms 检测滞后,靠平移校准消除。 - 切分:
fine_cut(-33dB/0.06s/0.10s)细切 →merge_to(N+1)→ 丢首碎屑(<0.35s 且与后块间隙 >0.15s 的预发音残响)→merge_to(N)→ 模式校验循环(最多 6 轮):长句位(≥5 字)时长 <1.30s 判违例;短语位时长 >1.25s → 块内最大谷切开(两侧各 ≥0.28s 才有效)。直到 N/N 全过,否则报错停止。 - head 检测:块内首帧 ≥ peak-18dB。块起点(能量谷边界)≠ 发声起点,差 30-80ms,搬运必须用 head 对齐。
- 落点:首句 = 拍序列上离用户要求时刻最近的真拍位(实例:"2 秒半左右"→ 2.688s);offsets 逐句累加
GAP_BEATS(连贯密集版 [2,2,4] = 1.0/1.0/2.0s);防重叠 = 从冲突句起整体后移整数拍(保持落点在拍上)。 - 搬运:
样本索引 = 帧索引 × HOP(铁律!);head 对齐 dst;首尾 8ms 线性淡化防爆音。 - 自验证(读回输出文件测,不信搬运数学):窗口 [dst-0.06, dst+0.20] 先判空(全零 → seg.max()=-inf → 假偏差);合格线 median ≤10ms、每落点 mod 拍周期 ≈ BEATPHASE(±5ms)、间隔集合 = GAPBEATS 去重、零重叠。
- 混音:伴奏×0.70 + 人声×1.25,峰值限幅 0.98。
- click 对轨:1500Hz 短音 = 真实拍点、2600Hz = 短语头,叠加伴奏+人声——用户耳朵终审用。
- 报告 JSON:每短语 word/dst/dur/src + beats 数组 + 参数快照,用户报短语号即可单点重排。
参数表(复用时改哪里)
| 参数 | 实战值 | 复用规则 |
|---|---|---|
BEAT_PHASE | 0.188 | 每首重测(直方图裁决),禁止沿用 |
GAP_BEATS | [2,2,4] | 密度口味参数;问参考版真实气口,不从第三方视频猜 |
| 首句目标时刻 | 2.5s 左右 | 用户指定,取最近真拍位 |
| 短语数 / 字数模式 | 36 / [3,3,7]×12 | 按新文案的标点短语改 |
| 模式校验阈值 | 短语位<1.25s / 长句位>1.30s | 按新文案字数模式定 |
| 切分阈值 | -33dB/0.06s/0.10s | 换音源先跑 diag_silence.py 诊断 RMS 分布 |
| 混音比 | 0.70 / 1.25 | 口味 |
铁律(每条都是返工换来的)
- 切分粒度 = 标点短语,短语内零处理。整句搬运被用户否("根本没切开");逐音节拉伸毁 flow 也被否。允许的变速只有 Stage A 全局一次校正。
- tempo 失配是"怎么挪都对不上"的唯一根因,必须先测速再动手;诊断失配看残差 vs 时间漂移趋势(median 有 step/4 均匀地板,会掩盖失配)。
- 拍相位必须从伴奏音乐事件直方图实测,不继承参考版/原版视频的手摆相位(系统性 70-110ms 偏差)。
- 密度看真实气口 = 句头间隔 - 前句时长,不是句头间隔本身。7字句 1.8s + 间隔 2.0s = 气口 0.2s(连贯);间隔 3.0s = 1.2s 真空档("空的节奏")。"连贯密集" = 气口 <1 拍。密度反馈迭代时先量参考版气口分布并出示数字。
- 落点必须整数拍步进:非整数拍间隔(如 1.5s=3拍)让落点强弱位置每句乱跳,数学在网格、音乐不在点上。
- 帧索引×HOP 才是样本;复测先判空窗口;交付前必须读回输出文件复测。
- 口味参数全部常量化(GAP_BEATS/首句时刻/混音比),用户反馈 = 一行改参重跑,不改逻辑。
引擎选型(合成脉冲实测数据)
| 引擎 | 实测 | 结论 |
|---|---|---|
rubberband CLI --timemap | 大比例段漂移 250ms(R2/R3 同病) | 禁用 |
| ffmpeg 分段 atempo | 每段累积误差 26ms,10 段漂 240ms | 禁用 |
| rubberband 均匀 `-t` 拉伸 | 输出样本数精确 <5ms | ✅ 唯一采用 |
RubberBand 4.0.0 安装:https://breakfastquay.com/files/releases/rubberband-4.0.0-gpl-executable-windows.zip (Windows 预编译;其他平台见 breakfastquay.com)
交付物(5 件)
mix.wav主交付(伴奏+对齐人声);vocal_aligned_phrases.wav纯对齐人声(可进 DAW);check_click.wavclick 验收版;map_1/2.png验证图(蓝=包络,红=拍网格,橙=短语头,中文标注需设置中文字体);align_report.json落点表。
沟通要点:必附 click 让用户自己验证卡点;报短语号可单点重排;逐字残差 p90 ~55ms 属 AI 生成人声固有松散(真人参考 ~34ms),如实说明。
新曲目复用(5 步)
- 混音文件先 demucs 分离人声;
- 跑
measure_tempo.py测双方速度 → 失配则global_tempo_fix.py全局校正,得vocal_aligned.wav; - 准备
dna_card.json+lyrics_final.json(BEAT_PHASE 必须重测); - 跑
align_phrases.py,检查自验证输出(median、EMPTY/<<< 标记、overlap 行、mode check 是否 pass); - 跑
verify_alignment.py出验证图 + 交付 5 件 + click 让用户耳朵终审。

