你是否刷到过那种“乐谱会跟着音乐自动高亮”的演奏视频?播放音乐时,五线谱上的音符一个接一个亮起,仿佛乐谱自己在跟着声音往前走。尤其是一些巴赫作品的钢琴改编版,密集的对位声部在谱面上逐层展开,既有教学价值,也很适合做音乐内容创作。但如果想自己动手,为 J.S.巴赫《勃兰登堡协奏曲第二号第一乐章(Allegro)》的钢琴独奏改编版制作一套曲谱同步内容,不少人会卡在三个地方:素材去哪里找、音频和 MIDI 怎么对齐、同步高亮怎么渲染。
这篇文章围绕“曲谱同步”这条完整链路展开,以巴赫 BWV 1047 第一乐章钢琴改编为例,从乐谱与音频的时间表示、节拍检测、MIDI 事件解析,到最终视频输出,整理一份可以照着做的实操教程。内容不依赖某套商业软件,也不要求你精通音乐理论,只要愿意动手,就能在自己电脑上跑通从乐谱到“会动的谱子”的全流程。
如果你是音乐科技方向的开发者、钢琴教学视频制作者,或者只是对乐谱跟随(Score Following)感兴趣,这篇文章都值得看完。
1. 曲谱同步是什么:从一段“会动的乐谱”说起
1.1 曲谱同步要解决什么问题
曲谱同步,简单说就是把音频播放进度和乐谱位置准确地对应起来。实时场景中,它叫“乐谱跟随”;离线场景中,它通常叫“乐谱音频对齐”。两者的核心目标一致:给定一段音频和一份乐谱,算出音频中的每一秒对应乐谱中的哪个音符、哪个小节。
这项工作看起来很直觉,做起来却不容易。音频是连续波形,乐谱是离散符号,前者包含演奏速度波动、力度变化、踏板噪声、录制环境混响,后者只有相对固定的节奏关系。要让两者“对表”,不能简单靠人工拖时间轴,需要一套可计算的流程:
- 把乐谱信息转换成时间事件,例如 MIDI 音符或 MusicXML 音符。
- 从音频中提取音乐特征,例如起始点(Onset)、节拍点(Beat)、速度(Tempo)。
- 用对齐算法把两类序列匹配起来。
- 按对齐结果渲染高亮或滚动画面。
曲谱同步的典型应用场景包括练琴跟谱、钢琴教学演示、音乐数据标注、音乐表演分析。对内容创作者来说,最常见的需求就是把一份钢琴改编谱做成“声音播放到哪里,谱面高亮到哪里”的视频。
1.2 BWV 1047 第一乐章为什么适合做案例
约翰·塞巴斯蒂安·巴赫的《勃兰登堡协奏曲》创作于科滕时期,大约在 1721 年汇编完成并献给勃兰登堡-施韦特侯爵。其中第二号(BWV 1047)采用 F 大调,三个乐章分别是 Allegro、Andante 和 Allegro assai。第一乐章是典型的巴洛克协奏曲快板,节奏鲜明,主题动机非常清晰,全曲建立在 F 大调主和弦分解与对位交织之上。
为什么拿这部作品做曲谱同步案例?因为它的音乐素材高度可预测。第一乐章开头的主题围绕 F-A-C-F 的分解和弦展开,无论你听到的是小号声部还是整体合奏,都能较容易地定位到乐谱中的对应位置。对于钢琴独奏改编来说,原曲的小号、竖笛、双簧管、小提琴和通奏低音全部压缩到两只手,声部层次更多,反而更能检验对齐效果。
钢琴改编谱的常见思路是:右手承担高音小号主题、长笛/双簧管的对位旋律,左手承担通奏低音与和声填充,中声部根据手指跨度适当省略或重组。不同版本可能差异明显,但这不影响曲谱同步流程,因为流程处理的是“乐谱事件”而不关心具体改编风格。
1.3 完整技术链路拆解
本文采用的路径是“音频节拍检测 + MIDI 时间戳解析 + 可视化渲染”。它不需要复杂的录音室设备,也不需要深度神经网络,普通电脑加上 Python 环境就能完成。
整条链路大致如下:
- 第一步:准备同一首作品的钢琴改编 MIDI 和对应音频。两者必须来自同一演奏版本,否则节拍位置对不上。
- 第二步:用 Python 读取 MIDI,把音符事件转换成“时间 + 音高 + 力度”的结构化数据。
- 第三步:用 librosa 对 WAV 音频做节拍跟踪,得到节拍时间点。
- 第四步:以 MIDI 时间为基准,对比音频节拍点,通过动态时间规整(DTW)等方式校正偏差。
- 第五步:把音符与节拍点绘制成钢琴卷帘或五线谱高亮画面。
- 第六步:用 FFmpeg 把画面和音频合成为最终视频。
下面先解决环境问题,再拆解每一步的原理,最后给出完整的实战流程。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境准备与素材整理
2.1 软件环境说明
本文示例代码以 Python 3.8+ 为基础,主要依赖以下库:
- mido:读取和解析 MIDI 文件。
- numpy:数值计算,处理数组和节拍数据。
- matplotlib:绘制波形、节拍点和简单可视化。
- librosa:音频加载、节拍跟踪、起始点检测。
- opencv-python(可选):逐帧渲染视频时使用。
- FFmpeg:音视频合成。
安装命令:
bash复制pip install mido numpy matplotlib librosa opencv-python
FFmpeg 需要单独安装,Windows、macOS、Linux 都有对应版本。具体版本会随着时间更新,以上库的 API 也可能在小版本之间有变化,运行示例时如果遇到“函数不存在”或“参数名变更”,建议先确认为当前环境的官方文档,再对 API 名称做小幅度调整。
MuseScore 是一款免费开源的五线谱编辑软件,本文用它来完成钢琴改编谱的整理和音频导出。你可以使用你熟悉的任何乐谱软件,只要它能导出 MIDI 和 WAV/MP3 即可。
2.2 素材来源与版权提醒
巴赫的作品本身已经进入公共领域,原始乐谱可以在 IMSLP 等公共乐谱库找到。但需要注意:
- 原始手稿和古乐谱是公共领域的,可以自由使用。
- 现代出版的“编订版”可能带有人为的指法、力度、踏板标记,是否受版权保护视出版社而定。
- 你使用的钢琴独奏改编版本如果来自当代作者,需要确认授权。
- 录音更复杂,商业录音通常受邻接权保护,不建议直接拿去二次分发。
最稳妥的方式是:从公共领域的 MIDI 文件出发,自己用 MuseScore 调整成钢琴改编版,导出音频和 MIDI。这样既保证了音频和 MIDI 的严格一致,又绕开了音源版权问题。
2.3 工程目录结构
建议按下面的结构组织项目,把原始素材和输出内容分开:
text复制bwv1047-sync/
├── assets/
│ ├── bwv1047_piano.mid
│ ├── bwv1047_piano.wav
│ └── score.xml
├── scripts/
│ ├── midi_events.py
│ ├── beat_sync.py
│ └── render_video.py
├── output/
│ ├── beat_track.png
│ └── bwv1047_sync_video.mp4
└── README.md
assets 放原始素材,scripts 放处理脚本,output 放生成结果。这样的好处是,无论后续怎么改进算法,原始材料都不会被覆盖。
2.4 从 MuseScore 导出一致速率的音频与 MIDI
曲谱同步最大的坑,是音频和 MIDI 来自不同版本,导致两边速度不一致。最稳妥的办法是用同一个工程导出。
在 MuseScore 中完成钢琴改编谱的编辑后,按以下流程导出:
- 在“播放”面板确认速度标记,建议把基础速度设为合理的 Allegro 区间,例如四分音符等于 120 BPM,具体以你的改编谱标记为准。
- 导出 WAV 文件,作为音频素材。
- 导出 MIDI 文件,作为音符事件素材。
- 可选:导出 MusicXML 文件,用于结构化的乐谱保存与后续程序处理。
导出时尽量使用 WAV 而不是 MP3。WAV 是无损格式,librosa 读取时不会引入压缩失真,节拍检测的结果也更稳定。
3. 核心原理:乐谱与音频怎么“对上时间”
3.1 音符事件的时间表示:从 MIDI 到秒
MIDI 文件内部并不直接记录“第几秒发生了什么”。它记录的是 tick(滴答数),单位取决于文件的 ticks_per_beat,也就是每个四分音符被划分为多少个 tick。播放速度则由 tempo 消息决定,tempo 的单位是微秒/四分音符。默认情况下,如果文件里没有 set_tempo 消息,通常按 120 BPM 也就是 500000 微秒/拍处理。
把 tick 转换成秒,核心公式是:
text复制秒 = tick / (ticks_per_beat) * (tempo / 1000000)
mido 提供了现成函数 tick2second,直接传入 tick、ticks_per_beat 和 tempo 即可。
这里需要特别提醒:古典音乐 MIDI 在曲中可能多次出现 set_tempo 消息,演奏速度不是全程恒定。简单处理时可以用第一个 tempo 或固定 tempo 计算,但严谨处理时应该在遍历消息的过程中不断更新当前 tempo。
3.2 MusicXML:把纸质乐谱变成结构数据
MIDI 适合表达“什么时间、按哪个音”,但它不擅长表达五线谱上的调号、拍号、连音线、强弱记号。MusicXML 是一种面向乐谱的结构化格式,可以把纸质乐谱翻译成机器可读的数据。
下面是一个最简单的 MusicXML 片段,表示 F 大调、4/4 拍、高音谱表中的 F4 四分音符:
xml复制<?xml version="1.0" encoding="UTF-8"?>
<score-partwise version="3.1">
<part-list>
<score-part id="P1">
<part-name>Pno.</part-name>
</score-part>
</part-list>
<part id="P1">
<measure number="1">
<attributes>
<divisions>4</divisions>
<key><fifths>-1</fifths></key>
<time><beats>4</beats><beat-type>4</beat-type></time>
<clef><sign>G</sign><line>2</line></clef>
</attributes>
<note>
<pitch><step>F</step><octave>4</octave></pitch>
<duration>4</duration>
<type>quarter</type>
</note>
</measure>
</part>
</score-partwise>
fifths 为 -1 表示调号为一个降号,也就是 F 大调。divisions 表示一个四分音符分成的 tick 数,此处设成 4,所以时值 4 正好是一个四分音符。实际的项目中,MusicXML 通常由 MuseScore 直接导出,不需要手写,但理解它的结构对后续做“乐谱高亮”很有帮助。
3.3 音频侧的特征提取:起始点与节拍跟踪
音频输入电脑后是一个波形数组。要对齐到乐谱,不可能直接拿波形和音符比较,需要先降维成音乐特征。
比较常用的两种特征:
- 起始点强度(Onset Strength):描述某一时刻是否有新的音符开始。在复调音乐中,多个声部同时发声时会有明显的能量突变。
- 节拍点(Beat):描述稳定的节拍网格,一般情况下,节拍点会落在强拍或次强拍上。
librosa 提供了完整的工具链。读取音频后,可以用 librosa.beat.beat_track 一次性得到估计速度 BPM 和节拍位置,其中节拍位置以帧为单位,再通过 librosa.frames_to_time 转换为秒。下面是核心代码:
python复制import librosa
import numpy as np
# 读取音频,统一采样率
y, sr = librosa.load('assets/bwv1047_piano.wav', sr=22050)
# 节拍跟踪
tempo, beat_frames = librosa.beat.beat_track(y=y, sr=sr)
# 部分版本返回的 tempo 可能是单元素数组,这里统一取标量
tempo = float(np.atleast_1d(tempo)[0])
# 帧号转成时间
beat_times = librosa.frames_to_time(beat_frames, sr=sr)
print(f"估计速度: {tempo:.2f} BPM")
print("前 20 个节拍点:")
for i, t in enumerate(beat_times[:20]):
print(f" beat {i:3d}: {t:.3f}s")
为什么把采样率设为 22050?这是 librosa 的常见默认采样率,既能保留音乐中绝大部分频率内容,又不会让计算量过大。如果你想要更高精度,可以用 44100,但节拍检测结果不一定因此更好,因为节拍检测主要依赖低频包络和能量变化。
3.4 对齐算法:动态时间规整(DTW)
有了音频节拍点和 MIDI 音符时间后,还需要处理两者之间的局部速度差异。同一个演奏版本在音频中可能有细微的渐快或渐慢,不能指望每个节拍都严格落在 MIDI 时间上。
动态时间规整(Dynamic Time Warping,DTW)是音乐对齐中的经典方法。它的思想是:允许两个序列在时间轴上局部伸缩,找到一条累积距离最小的路径。
假设有两个序列 a 和 b,DTW 的计算可以用动态规划完成:
python复制import numpy as np
def dtw_distance(seq_a, seq_b):
m, n = len(seq_a), len(seq_b)
cost = np.full((m + 1, n + 1), np.inf)
cost[0, 0] = 0.0
for i in range(1, m + 1):
for j in range(1, n + 1):
d = abs(seq_a[i - 1] - seq_b[j - 1])
cost[i, j] = d + min(
cost[i - 1, j],
cost[i, j - 1],
cost[i - 1, j - 1]
)
return cost[m, n]
# 示例:两个接近但略有伸缩的节拍间隔序列
a = [0.5, 0.5, 0.5, 0.5]
b = [0.48, 0.52, 0.49, 0.51]
print(dtw_distance(a, b))
这个函数返回两个序列的最小累计距离。距离越小,说明音频节拍和乐谱节拍越吻合。完整对齐不仅需要距离,还需要回溯路径,在实战中通常使用专门算法或借助音乐信息检索工具完成,但理解核心原理已经足够。
3.5 高亮渲染的基本思想
曲谱同步视频的最后一公里是渲染。高亮渲染的思路很简单:在每一帧画面里,把当前时间对应的音符画成醒目颜色,其他音符用浅色表示。
如果用五线谱渲染,需要把 MusicXML 中的音符坐标映射到屏幕坐标。如果只做钢琴卷帘,则更简单:横轴是时间,纵轴是音高,音符用矩形块表示。五线谱更美观,代码实现更复杂;钢琴卷帘更容易,适合快速验证对齐效果。
本文的实战部分先用钢琴卷帘方案验证同步,然后你可以替换成五线谱高亮。
4. 实战:巴赫勃兰登堡协奏曲第二号第一乐章钢琴改编曲谱同步
4.1 准备钢琴独奏改编 MIDI
假设你已经在 MuseScore 中整理好了钢琴改编谱。如果没有现成谱,可以打开原版乐队总谱,新建钢琴谱,把旋律声部复制到右手,把通奏低音复制到左手,中声部适当取舍。
在操作时注意以下几点:
- 将小号和弦乐组的主题放在右手最高声部,这是最容易被听众辨认的旋律。
- 左手保持低音线条完整,巴洛克音乐的低音是节奏与和声的基石。
- 不用把每个声部都原样保留,钢琴十指限制决定了必须做和声简化。
- 设置好速度标记,让 MuseScore 导出的 WAV 和 MIDI 使用同一速度。
完成后导出:
bash复制# 假设你已经在 MuseScore 中打开工程
# 导出 MIDI:assets/bwv1047_piano.mid
# 导出音频:assets/bwv1047_piano.wav
4.2 解析 MIDI 时间事件
接下来用 Python 读取 MIDI,输出每个音符的起始时间和力度。脚本保存为 scripts/midi_events.py:
python复制import mido
from mido import MidiFile
def midi_notes_to_seconds(midi_path):
mid = MidiFile(midi_path)
ticks_per_beat = mid.ticks_per_beat
tempo = 500000 # 默认 120BPM,单位为微秒/拍
notes = []
current_tick = 0
for msg in mid:
current_tick += msg.time
if msg.type == 'set_tempo':
tempo = msg.tempo
continue
if msg.type == 'note_on' and msg.velocity > 0:
seconds = mido.tick2second(current_tick, ticks_per_beat, tempo)
notes.append((seconds, msg.note, msg.velocity))
return notes
if __name__ == '__main__':
notes = midi_notes_to_seconds('assets/bwv1047_piano.mid')
print("前 20 个音符事件:")
for sec, note, vel in notes[:20]:
print(f" {sec:8.3f}s note={note:3d} vel={vel}")
print(f"共解析到 {len(notes)} 个音符")
代码逻辑说明:
mid.ticks_per_beat是 MIDI 文件的时基。current_tick记录当前消息的绝对 tick 位置。- 遇到 set_tempo 时更新速度,保证后续 tick 转秒使用正确的 tempo。
note_on且 velocity 大于 0 才作为有效音符,velocity 为 0 的 note_on 实际上等于 note_off。
如果你的 MIDI 文件里大量使用 note_off 而不是 velocity=0,上面代码同样能处理,因为 note_off 不会被当作新增音符。
4.3 检测音频节拍并导出时间表
音频侧的关键任务是得到节拍点。脚本保存为 scripts/beat_sync.py:
python复制import librosa
import numpy as np
y, sr = librosa.load('assets/bwv1047_piano.wav', sr=22050)
tempo, beat_frames = librosa.beat.beat_track(y=y, sr=sr)
tempo = float(np.atleast_1d(tempo)[0])
beat_times = librosa.frames_to_time(beat_frames, sr=sr)
np.savetxt('output/beat_times.csv', beat_times, delimiter=',', fmt='%.6f')
print(f"估计速度: {tempo:.2f} BPM")
print(f"检测到节拍数: {len(beat_times)}")
print("前 10 个节拍时间:")
for i, t in enumerate(beat_times[:10]):
print(f" {t:.3f}s")
生成 output/beat_times.csv 的目的是把它作为音频侧的时间基准。后续渲染时,可以以这些节拍点对音符时间做局部校正。
4.4 用波形图验证节拍检测结果
验证是一个很重要的习惯。不要直接进渲染,先看波形图里的节拍线是否合理。写一个简单的绘图脚本:
python复制import matplotlib.pyplot as plt
import librosa
import numpy as np
y, sr = librosa.load('assets/bwv1047_piano.wav', sr=22050)
tempo, beat_frames = librosa.beat.beat_track(y=y, sr=sr)
tempo = float(np.atleast_1d(tempo)[0])
beat_times = librosa.frames_to_time(beat_frames, sr=sr)
time_axis = np.linspace(0, len(y) / sr, len(y))
plt.figure(figsize=(12, 4))
plt.plot(time_axis, y, alpha=0.5, linewidth=0.5)
for bt in beat_times:
plt.axvline(bt, color='red', alpha=0.7, linewidth=0.8)
plt.title("BWV 1047 Mvt.1 Piano Transcription - Beat Tracking")
plt.xlabel("Time (s)")
plt.ylabel("Amplitude")
plt.tight_layout()
plt.savefig('output/beat_track.png', dpi=150)
运行后打开 output/beat_track.png,红色竖线应该大致落在每个强拍的起始位置。如果偏差很大,先检查音频是不是与 MIDI 来自同一版本,再考虑调整 librosa 的参数,比如用 librosa.beat.beat_track 的 start_bpm 参数给一个范围提示。
4.5 渲染最简钢琴卷帘高亮视频
钢琴卷帘渲染其实就是一个循环:逐帧读取当前时间,把落在当前时间区间内的音符画成高亮色。下面是一个示例框架:
python复制import cv2
import numpy as np
def draw_frame(note_rects, current_time, width=960, height=540):
frame = np.zeros((height, width, 3), dtype=np.uint8)
for rect in note_rects:
start, end, pitch = rect
color = (80, 80, 80) # 默认灰色
if start <= current_time < end:
color = (0, 200, 255) # 高亮橙色
x0 = int(start * scale_x)
x1 = int(end * scale_x)
y0 = int((max_pitch - pitch) * scale_y)
y1 = y0 + note_height
cv2.rectangle(frame, (x0, y0), (x1, y1), color, -1)
return frame
这只是一个示意,实际使用中需要根据 MIDI 里的音符 start/end 时间构造矩形,并处理画面比例、滚动偏移等细节。更简单的做法是先用 matplotlib 保存静态谱面,再用 OpenCV 叠加高亮框。
4.6 用 FFmpeg 合成音视频
画面渲染完成后,需要把视频和音频合成在一起。假设你的画面文件是 output/frames.mp4,原始音频是 assets/bwv1047_piano.wav,在项目根目录执行:
bash复制ffmpeg -i output/frames.mp4 -i assets/bwv1047_piano.wav \
-c:v libx264 -pix_fmt yuv420p -c:a aac -shortest \
output/bwv1047_sync_video.mp4
参数说明:
-c:v libx264:视频编码使用 H.264,兼容性最好。-pix_fmt yuv420p:保证视频在主流播放器中能被识别。-c:a aac:音频编码为 AAC。-shortest:输出时长以较短的输入为准,避免画面或音频尾部多余空白。
4.7 运行顺序与验证标准
按照以下顺序执行完整流程:
bash复制python scripts/midi_events.py
python scripts/beat_sync.py
python scripts/render_video.py
ffmpeg -i output/frames.mp4 -i assets/bwv1047_piano.wav \
-c:v libx264 -pix_fmt yuv420p -c:a aac -shortest \
output/bwv1047_sync_video.mp4
验收时重点看三个位置:
- 开头主题进入时,高亮是否落在 F-A-C 分解和弦上。
- 中间复调密集段,乐谱是否在错误的低音上提前点亮。
- 结尾处,最后一个和弦是否与音频同步结束。
如果你发现局部位置始终对不齐,不要只调渲染参数,先回去检查节拍检测结果和 MIDI 速度设置。
5. 常见问题与排查思路
曲谱同步的报错往往不是“程序崩溃”,而是“结果不对”。下面整理了几个高频问题:
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| 音频和 MIDI 速度明显对不上 | 两个文件来自不同演奏版本或不同速度设置 | 在 MuseScore 同一次导出中生成 MIDI 和 WAV,统一速度 |
| librosa 节拍点整体偏移 | 音频开头有空白或强混响 | 裁掉音频开头静音,或先做起始点检测定位第一个音符 |
| 解析 MIDI 得到的事件很少 | 文件用了大量 note_off,或音符集中在某一音轨 | 检查 MIDI 格式,遍历全部音轨而不是只读第一个 track |
| 高亮与音频偏差在缓慢变化 | 演奏速度在中途逐渐加快或放慢 | 使用 DTW 做全局对齐,而不是简单用固定速度偏移 |
| 视频没有声音 | FFmpeg 没有正确合并音轨 | 确认输入路径正确,使用 -c:a aac -shortest 后再看输出 |
| MuseScore 导出的音频无法被 librosa 读取 | 采样率或编码格式不兼容 | 用 librosa.load(..., sr=None) 查看原始采样率,必要时转码为 WAV |
关于 librosa 安装失败,最常见的解决方案是新建一个干净的虚拟环境:
bash复制python -m venv venv
source venv/bin/activate
pip install librosa
如果你用的是较新版本的 librosa,某些函数参数可能有所调整,例如 beat_track 的返回类型在不同版本中不完全一致。遇到这类问题,优先打印返回值的类型,再决定是否用 np.atleast_1d 或 float 转换。
6. 最佳实践与工程建议
6.1 素材管理:把原始文件和生成结果分开
不要把原始 MIDI、修改后的音乐工程、导出视频全部堆在一个文件夹。建议像前面目录结构那样,使用 assets、scripts、output 三个顶层目录。这样做的好处是:每次实验不会覆盖原始文件,脚本出错时也更容易回滚。
6.2 对齐质量:先用波形图校准,再谈渲染
自动节拍检测在巴赫这样声部密集的作品上不是百分百可靠。渲染视频之前,一定要先画波形图和节拍点,肉眼确认节拍线是合理的。如果节拍检测本身是错的,后面所有高亮动画都会跟着错,而且很难通过渲染层面修复。
6.3 性能优化:长音频不要一次性渲染
整首乐章长度可能在五分钟左右,如果逐帧用 Python 画图,帧率会很低。优化思路包括:
- 先用 librosa 计算节拍点,避免在每一帧中重新加载和处理音频。
- 把音符矩形数组预先计算好,渲染循环只做矩形绘制。
- 使用 OpenCV 绘制,而不是在每一帧调用 matplotlib。
- 低分辨率预览时先渲染 640x360 的版本,确认没问题后再渲染高清版。
6.4 版权与发布
巴赫原曲属于公共领域,但钢琴改编版本、录音和视频画面可能涉及版权。如果你准备公开发布:
- 优先使用自己用 MuseScore 生成的音频和 MIDI。
- 在视频简介中注明原曲为 J.S.巴赫 BWV 1047,并写明钢琴改编来源。
- 不要直接使用商业录音作为同步音频。
6.5 安全与权限
本文涉及的所有音频处理都在本地文件上完成,没有调用任何在线接口,也没有绕过任何平台限制。如果你后续准备把生成的视频上传到视频平台,需要自行确认平台对音乐类内容的授权要求。涉及第三方素材的下载和转换,请遵守对方网站的条款,只使用明确授权或公共领域的资源。
7. 总结与学习路线
通过这篇教程,你可以完成一条完整的曲谱同步链路:从 MuseScore 中的巴赫勃兰登堡协奏曲第二号第一乐章钢琴改编谱出发,导出同一版本的 MIDI 和 WAV,用 mido 解析 MIDI 音符事件,用 librosa 检测音频节拍,再用可视化渲染和 FFmpeg 合成视频。
下一次遇到类似需求时,可以按这个顺序排查:先确认音频和 MIDI 是否同源,再检查节拍检测是否准确,最后才考虑渲染和编码问题。
如果想继续深入,建议按下面的路线学习:
- 音乐信息检索基础:理解起始点检测、节拍跟踪、调性分析。
- 动态时间规整进阶:实现 DTW 的回溯路径,输出音符到音频帧的一一映射。
- 五线谱高亮渲染:学习 MusicXML 解析与绘图,把钢琴卷帘替换成真正的五线谱。
- 实时乐谱跟随:把离线对齐换成流式处理,用逐帧音频特征驱动高亮。
- 把这套流程推广到其他巴赫作品,例如《平均律钢琴曲集》或《哥德堡变奏曲》,这些作品的织体和速度变化会带来新的挑战。
如果你在做巴赫钢琴改编曲谱同步时遇到其他问题,欢迎在评论区描述你的具体现象,把报错信息、MIDI 来源和节拍检测结果发出来,一起分析解决。
