--- name: rap-beat-align description: 把 AI 生成/节奏忽快忽慢的说唱人声精准对齐到伴奏节奏(v13 实战验收版)。适用:rap 对拍、人声铺 beat、AI 语音卡点、参考版对齐。触发词:对拍、对齐伴奏、卡拍、rap 对齐、人声铺 beat、说唱对齐。核心:标点短语切分(短语内零处理)→ 全局测速校正 → 真实拍点落点(直方图裁决相位)→ 整数拍步进落点 → 样本级搬运 → 读回复测 → click 验收。 --- # Rap Beat Align(说唱人声对伴奏节奏对齐) 经 v4→v13 十三轮实战迭代打磨的完整方法论。**一句话总纲**:切短语(标点粒度)→ 校速度(全局一次)→ 找真拍(直方图裁决相位)→ 密集落点(整数拍步进)→ 样本级搬运 → 读回复测 → click 验收。 **在 rap-clone-workflow 中的角色:S5 对拍站。** 输入 = `04_tts/dry_vocal.wav` + `02_analysis/dna_card.json`(BEAT_PHASE 每首重测、GAP_BEATS = 卡内 gap_beats、首句时刻 = 卡内 first_phrase_beat)+ `03_lyrics/lyrics_final.json`(短语表);产出写入 `05_align/`。独立使用时按下方"输入"节自备素材即可。 ## 输入 1. **人声**:AI 生成干声,或混音文件经 demucs 分离的人声轨; 2. **伴奏**:wav/mp3/flac; 3. **歌词文本**:提供标点短语数 N 和字数模式(实例:36 短语 = [3字,3字,7字]×12); 4. 可选:用户手动对齐的参考版(**密度金标准,但相位不可继承**,见铁律 3)。 ## 快捷路径:preset 一键模式(粉丝/小白推荐) `presets/wannengjiao.json` 预设卡把一首已验收伴奏的全套调好的参数打包了(伴奏 assets、拍相位 0.188、首句第 5 拍、按字数查表的落点密度、混音比)——**用户只需要人声 + 纯文本歌词**: ```bash # Stage A 校速(失配才需要) python scripts/measure_tempo.py --inst assets/beats/wannengjiao_120bpm.flac --vocal vocal.mp3 --out tempo.json python scripts/global_tempo_fix.py --vocal vocal.mp3 --acc assets/beats/wannengjiao_120bpm.flac --tempo-report tempo.json --rb rubberband --out-dir out/ # Stage B 一键对齐:--lyrics-text 按标点自动切句(逗号=短句/句号=长句),免做 lyrics_final.json python scripts/align_phrases.py --preset wannengjiao --vocal out/vocal_aligned.wav --lyrics-text lyrics.txt --out-dir 05_align/ ``` preset 模式要点: - `gap_rule: by_prev_chars` + `gap_map`(3字→+2拍、7字→+4拍)= 「按前一短语字数定间隔」的泛化规则,任意 [3,3,7] 组合结构(含结尾 [7,7,7]/[7,7])自动复现 [2,2,4] 密度,**零逻辑改动适配新歌词**; - `--lyrics-text` 自动生成 frozen=true 的短语表并校验数字风险; - 伴奏 64s FLAC 无损打包(无解码延迟,拍相位参数直接有效);beat_track 在弱拍 intro 丢拍时脚本自动按校准相位均匀重建网格(±60ms 内吸收实测拍); - 双结构实测:36 短语 median -2.3ms / max 18ms;35 短语(11×[3,3,7]+[7,7])median -2.3ms,结尾组落点与逐单验收版差 ≤3ms。 - 打包伴奏版权归原作者(见 assets/beats/NOTICE.md),仅限学习演示;自备伴奏走全参模式或照 preset 卡写自己的卡。 ## 两阶段流水线 **Stage A:精确测速 + 全局校正**(`scripts/measure_tempo.py` + `scripts/global_tempo_fix.py`) **Stage B:标点短语级对齐**(`scripts/align_phrases.py` 一步到位:切分→落点→搬运→自验证→混音→click→报告) ### Stage A 步骤 1. **角色判定**:人声 300-4kHz 无低频/高频截断;伴奏全频带低频贯穿。混音文件先分离:`python -m demucs --two-stems=vocals -n htdemucs -o demucs_out `。 2. **精确测速(双方)**:`measure_tempo.py` 做 onset 级自由周期 lattice 拟合(扫描周期+圆形方差找相位)+ 迭代回归精化,残差 med <10ms 才算锁定。**绝不信 librosa beat_track 的 tempo 报告值**——swing 节奏陷阱:实例伴奏真值 120.01 BPM,它报 117.45。 3. **失配判定**:双方速度差 >0.3% 即失配。实例:人声 131.23 vs 伴奏 120.01 BPM(快 9.35%)——此时切句/挪句头/模式重排全部无效(20 秒处漂出 ~2 秒),**唯一正解 = 全局一次性校正**。 4. **全局校正**:`global_tempo_fix.py` 调 rubberband 均匀拉伸(音高不变,保留所有字间相对关系)。向用户说明:这与被否掉的"逐字破坏性拉伸"本质不同;音色轻微变软,不可接受则按伴奏 BPM 重新生成人声。 ### Stage B 步骤(`align_phrases.py` 内置) 1. **拍序列**:`beat_track(units='time')` → 整体平移校准到 BEAT_PHASE → 尾部按拍周期外推补齐 → 自检 onset 命中率(随机相位基线 ~12%,实测 46% 合格)。 2. **BEAT_PHASE 裁决(每首必重测,禁止沿用旧值)**:`onset_detect(hop=128, n_fft=1024)` 事件时间 mod 拍周期 → 16-bin 直方图 → 事件聚类即真相位。实例:0.188s(拍头)+0.438s(反拍)双峰集中 139/153 事件,其余相位为空。**用户参考版的手摆相位有 70-80ms 系统性提前,绝不能当金标准继承**——沿袭它导致人声永远在拍前冲。beat_track 相位自带 ~30ms 检测滞后,靠平移校准消除。 3. **切分**:`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 全过,否则报错停止。 4. **head 检测**:块内首帧 ≥ peak-18dB。块起点(能量谷边界)≠ 发声起点,差 30-80ms,搬运必须用 head 对齐。 5. **落点**:首句 = 拍序列上离用户要求时刻最近的真拍位(实例:"2 秒半左右"→ 2.688s);offsets 逐句累加 `GAP_BEATS`(连贯密集版 [2,2,4] = 1.0/1.0/2.0s);防重叠 = 从冲突句起整体后移整数拍(保持落点在拍上)。 6. **搬运**:`样本索引 = 帧索引 × HOP`(铁律!);head 对齐 dst;首尾 8ms 线性淡化防爆音。 7. **自验证(读回输出文件测,不信搬运数学)**:窗口 [dst-0.06, dst+0.20] 先判空(全零 → seg.max()=-inf → 假偏差);合格线 median ≤10ms、每落点 mod 拍周期 ≈ BEAT_PHASE(±5ms)、间隔集合 = GAP_BEATS 去重、零重叠。 8. **混音**:伴奏×0.70 + 人声×1.25,峰值限幅 0.98。 9. **click 对轨**:1500Hz 短音 = 真实拍点、2600Hz = 短语头,叠加伴奏+人声——用户耳朵终审用。 10. **报告 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 | 口味 | ## 铁律(每条都是返工换来的) 1. **切分粒度 = 标点短语,短语内零处理**。整句搬运被用户否("根本没切开");逐音节拉伸毁 flow 也被否。允许的变速只有 Stage A 全局一次校正。 2. **tempo 失配是"怎么挪都对不上"的唯一根因**,必须先测速再动手;诊断失配看残差 vs 时间漂移趋势(median 有 step/4 均匀地板,会掩盖失配)。 3. **拍相位必须从伴奏音乐事件直方图实测**,不继承参考版/原版视频的手摆相位(系统性 70-110ms 偏差)。 4. **密度看真实气口 = 句头间隔 - 前句时长**,不是句头间隔本身。7字句 1.8s + 间隔 2.0s = 气口 0.2s(连贯);间隔 3.0s = 1.2s 真空档("空的节奏")。**"连贯密集" = 气口 <1 拍**。密度反馈迭代时先量参考版气口分布并出示数字。 5. **落点必须整数拍步进**:非整数拍间隔(如 1.5s=3拍)让落点强弱位置每句乱跳,数学在网格、音乐不在点上。 6. **帧索引×HOP 才是样本;复测先判空窗口;交付前必须读回输出文件复测**。 7. **口味参数全部常量化**(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 件) 1. `mix.wav` 主交付(伴奏+对齐人声); 2. `vocal_aligned_phrases.wav` 纯对齐人声(可进 DAW); 3. `check_click.wav` click 验收版; 4. `map_1/2.png` 验证图(蓝=包络,红=拍网格,橙=短语头,中文标注需设置中文字体); 5. `align_report.json` 落点表。 沟通要点:必附 click 让用户自己验证卡点;报短语号可单点重排;逐字残差 p90 ~55ms 属 AI 生成人声固有松散(真人参考 ~34ms),如实说明。 ## 新曲目复用(5 步) 1. 混音文件先 demucs 分离人声; 2. 跑 `measure_tempo.py` 测双方速度 → 失配则 `global_tempo_fix.py` 全局校正,得 `vocal_aligned.wav`; 3. 准备 `dna_card.json` + `lyrics_final.json`(BEAT_PHASE 必须重测); 4. 跑 `align_phrases.py`,检查自验证输出(median、EMPTY/<<< 标记、overlap 行、mode check 是否 pass); 5. 跑 `verify_alignment.py` 出验证图 + 交付 5 件 + click 让用户耳朵终审。