☰
Python构建Grunge摇滚数据集:从手工扒谱到MIDI特征工程实践
2026/10/18 23:08:41 网站建设 项目流程

这是Python编曲实践的第十篇,前九篇分别处理过流行、电子、金属等不同风格的数据集,这一篇终于轮到Grunge。Grunge是那种乍听很糙、拆开很精的风格,满耳朵的失真吉他加咆哮式人声,很容易让人以为扒谱会很简单,反正就是几个强力和弦来回扫嘛。可真正把音频放进DAW逐轨拆解之后,我才发现这个流派的编曲密度相当高:节奏吉他在强力和弦里藏了大量切音、制音和微时值变化;贝斯经常脱离根音去跟鼓组形成对位;鼓组则在过门和动态对比上做足了文章。为了支撑后续的编曲风格分析与自动生成实验,我手工扒了一套Grunge摇滚数据集,范围锁定在Grunge时期四支代表乐队各自最具代表性的经典专辑上,音频、MIDI和元数据三层全部对齐。这篇把从选曲、扒谱、标注到Python处理的完整链路拆开讲清楚,适合同样在做音乐数据挖掘、风格生成,或者单纯想提升扒谱效率的朋友参考。

1. 为什么选Grunge:风格定位与数据价值

1.1 Grunge在摇滚谱系里的位置:介于朋克与金属之间的过渡带

Grunge这个词现在很多人把它当成一种“脏音色”的标签,但从编曲角度看,它是一种非常特殊的中间态。朋克讲究的是三和弦加直给的节奏,金属讲究的是高速交替拨弦和复杂小节,而Grunge恰好卡在两者之间:和声素材大多不超出强力五和弦的范畴,时值上却保留了朋克那种向前的推动力;吉他音色失真度很高,动态范围却不像金属那样被压得很平,主歌段经常收得很轻,副歌段直接砸下来。这种在同一个作品内部形成的巨大动态落差,是Grunge最值得分析的地方,也是很多算法不好处理的地方——如果你把一首歌整段丢进特征提取器,很容易因为整体方差太大而得到不稳定结果,所以做数据集时必须把这种动态结构显式地标注出来。

另外,Grunge对“段落感”的依赖远超其他摇滚分支。一首歌里经常出现安静分解和弦的主歌、突然变成失真墙的副歌、以及只有贝斯和鼓的桥段,这种清晰的编曲叙事结构非常适合验证段落边界识别、动态对比量化、以及唱段与器乐段落之间的过渡建模。做数据集的时候需要意识到这个特殊性:不能只记录音符本身,还要把段落的力度层级、失真状态、推弦范围这些“音色与演奏层”的信息一起标注进去。这也是为什么选Grunge而不是先做更规整的流行乐——它逼着你把数据模型设计得更立体。

1.2 四支代表乐队如何形成风格互补

我选的四支乐队,恰好代表Grunge里四种不太一样的写法,为了讨论方便,下面统一叫A队、B队、C队、D队。A队的核心是旋律化的Grunge,主唱歌词和吉他主题经常形成强呼应,编曲上习惯用一段清音分解和弦引入、再被失真墙覆盖的手法,乐句比较规整,适合作为入门标注样本。B队更强调节奏吉他的持续输出,Riff密集而且大量使用低音E弦上的单音连复段,和弦很少,但切音时值变化极多,扒起来非常费耳朵。C队的节拍思维更偏另类,经常在段落内部切换拍号,甚至会把副歌的重音放在反拍上,对量化策略的考验最大。D队则走的是阴沉路线,大量降半音或降全音调弦,和声上多使用五和弦与小调音阶的重叠,贝斯经常独立于吉他走一条低音旋律线。

乐队风格侧重和声特征节奏特征调弦情况
A队旋律化Grunge清音分解+失真墙规整、强调段落落差标准调弦为主
B队Riff驱动单音连复段为主切音密集、时值变化多标准调弦/降半音
C队偏另类/实验五和弦+非常规进行拍号切换、反拍重音降半音为主
D队阴暗氛围小调五和弦重叠低音线条独立降全音常见

四支乐队合在一起,基本覆盖了Grunge从偏流行、到偏金属、再到偏实验的完整光谱。我做数据集时没有平均分配曲目,而是有意让B队和D队稍微多收了几首,因为这两个方向的Riff特征更密集,能为后续风格分类和乐句生成提供更充分的样本。A队和C队的作品则更多用来检验模型能不能区分“旋律型”和“节奏型”Grunge,这四类样本互相校准,比单一乐队的曲目堆积要有价值得多。

1.3 专辑筛选的原则:既要代表性又要可扒性

选专辑的时候我给自己定了三条硬标准。第一是风格坐标代表性,优先选那些被公认为奠定乐队风格的专辑,录制质量也要够好,因为早期小样虽然够地下,但很多声部糊成一片,扒出来的音高和时值可信度很低。第二是每张专辑里的曲目不能全收,要挑声部分离度高的歌,比如有些歌吉他贝斯鼓挤在同一个频段,波形已经混死了,这种只能放弃。第三是控制重复度,同一张专辑里如果两首歌的结构几乎一样,只收一首,保证数据集的多样性。

实际操作下来,每支乐队最终收了8到12首不等,集中在他们第二、三张正式专辑以及各自最具代表性的那张商业专辑里,个别还覆盖了一张不插电现场录音。现场版的好处是编曲更精简,乐手之间的对位关系听得更清楚,适合用来做交叉验证——同一首歌在录音室版和现场版的区别,本身就是研究编曲自由度的高质量素材。整份数据集目前共38首曲目,总时长接近两个半小时,这个体量对训练一个轻量级风格模型、或者做乐句对比和和声风格分析来说绰绰有余。

2. 数据集的总体设计与标注规范

2.1 曲目构成:体量、段落与时长分布

38首曲目按四支乐队分布:A队8首、B队11首、C队7首、D队12首。每首平均时长3分50秒左右,最短的2分40秒,最长的一首接近6分钟——那首结构比较特殊,中间有大段游离的贝斯独奏,对标注工作是很典型的压力测试。段落层面全部按前奏、主歌、预副歌、副歌、桥段、过门、尾奏做了标记,没有明确段落名称的过渡部分统一标成“Interlude”,避免出现“段落类型”这种含糊字段。

BPM分布我也做了统计,整体落在75到120之间,中位数在96附近。这个速度区间比流行乐慢不少,但体感上并不拖,原因在于鼓组的8分或16分音符密集度很高,所以单看BPM容易低估它的信息量。标注的时长维度除了秒和拍数,还额外记录了小节数,方便后续按乐句做切片时不用重新数小节。每首歌的起始偏移也被记进了元数据,让音频切片和MIDI文件可以直接叠加播放。

2.2 音频与MIDI双轨制:为什么两种格式都要留

数据集里每首歌同时保存了两份文件:一份是从原曲中切出的音频分轨,WAV格式,44.1kHz采样率、16bit位深,按吉他、贝斯、鼓、人声四组切片;另一份是对应每一轨的MIDI文件,记录音符、时值、力度和控制器信息。两份文件用同一个文件名前缀加后缀区分,比如“track01_guitar_riff.wav”和“track01_guitar_riff.mid”,并在元数据里登记两者的起始时间偏移。

为什么不留纯MIDI?因为MIDI是符号化结果,音色感和演奏细节全丢了,后续如果要做音色迁移或者合成回放,音频切片是必不可少的监督参考。为什么要留MIDI?因为算法分析不能直接从波形里拿到可靠的音高序列和和弦标签,符号化之后做特征工程会省很多事。双轨制还有一个隐藏好处:可以用音频事件检测去辅助校验MIDI标注有没有漏音,比如波形里有一段明显的扫弦能量,但MIDI只写了两个音符,基本就是扒漏了,这种问题只靠听很难发现。

2.3 轨道拆分与元数据字段设计

轨道层面,吉他按“节奏吉他”和“主音吉他”分开,贝斯单独一条,鼓组按底鼓、军鼓、嗵鼓、踩镲、镲片五部件展开,人声只标主旋律线,和声背景单独放一个可选的伴唱轨。每条轨道都记录了一个“track_role”字段,值域包括riff、chord、bass、lead_vocal、backing_vocal、drum_component。这个字段后续做特征筛选时非常关键——把全部音符混在一起算直方图会丢失声部角色信息,按角色分开才看得出混音层次。

元数据用JSON存,每首歌一个文件,顶层字段包括band_id、album_id、track_title、bpm、key、tuning、time_signature、ending_type,然后是一个tracks数组,里面每项对应一条轨道。tuning字段在Grunge里极其重要,因为很多Riff听起来是个D5,其实是标准调弦下的E5按了两格,或者降全音调弦下的开放D5,如果不记录调弦,后面做音高统计会得出完全错误的结果。刚开始我只记了一个全局调弦,后来发现同一首歌里不同吉他轨也可能不同弦——有的歌节奏吉他降半音、主音吉他标准调弦,甚至贝斯还会用五弦贝斯补一个低音C,所以最后改成了轨道级字段,每条轨道单独记调弦方式。

3. 手工扒谱的核心流程:从试听对齐到MIDI落盘

3.1 扒谱前准备:BPM映射与段落标记

手工扒谱的第一步不是拿起MIDI键盘就录音,而是先把参考歌映射到工程网格上。我会先把原曲导入DAW,找到鼓组里最清晰的底鼓重音,用人耳判断大致速度,再用拍速检测功能微调到跟原曲完全同步。这一步急不得,BPM差0.5都会让后半段的音符时值持续累积偏移,等到扒第三段时网格已经完全对不上了,返工成本极高。

对齐之后给整首歌铺段落标记,颜色按段落类型区分。标记有两个作用:一是为后面逐段扒谱提供导航,二是为输出的元数据直接提供段落边界——我后来写了个脚本,直接从工程导出的标记表生成JSON里的段落字段,省去二次录入。段落标记尽量标在小节线起始处,不要标在中间,否则鼓手中途进拍的位置会变成半个小节,给后续小节计数和乐句切分都带来麻烦。

3.2 吉他轨:锁定根音、强力和弦与闷音时值

吉他Riff是Grunge的灵魂,也是手工扒谱里工作量最大的一块。我的习惯是先不弹,先反复听那一段,确定根音和低音走向,再判断它是强力五和弦、还是加了八度音、还是出现了三度的非强力和弦。Grunge里其实有不少大调小调三和弦的用法,只是失真度太高,很多人一听就默认全是五和弦——这种先入为主的判断会害死人,我曾经在同一首歌的连续两段里把一个小调三和弦听成五和弦,直到核对泛音列才反应过来。

确定音高后,在钢琴卷帘窗里输入,然后重点修时值。Grunge节奏吉他的难点在于闷音:很多连复段听起来是16分音符的密集扫弦,但实际录音中乐手会在每小节前半部分做制音,后半部分才放开,导致同样的谱面写法在听感上分成两段。处理办法是把音频切片循环到那一个拍子上,以半拍为单位A/B对比,同时看波形包络:制音段的波形起振更钝、峰值更平,放开弹的起振更锐、峰值带明显的瞬态。两种状态对应MIDI里是否需要标记闷音控制器事件,我会在第2号控制器上用连续值表示制音深度,而不是单纯用音符时值去假装,这样后续做合成回放时能如实还原闷音的音色变化。

3.3 贝斯与鼓组:先鼓后贝斯的对位策略

贝斯和底鼓在Grunge里经常是同音齐奏,所以我不分开扒,而是先扒底鼓,把所有底鼓重音的位置标好,再在上面铺贝斯音符。这样做的好处是贝斯音符的起始位置可以直接对齐到底鼓事件上,省掉很多微调。反过来先扒贝斯再对鼓也不是不行,但贝斯有时候会在音符中间有一点推弦或装饰音,起音位置不如底鼓干净,作为时间基准会引入额外误差。

鼓组标注我用的是DAW内置的鼓映射,底鼓在C1、军鼓在D1、嗵鼓按音高排在E1、F1、G1,踩镲在F#1,镲片在G#1附近,导出MIDI后通过音高就能区分部件。遇到过门里的滚奏,我会先把军鼓的16分音符连续段标出来,再回去修力度——滚奏的力度不是均匀的,通常是第一下重、中间轻、最后一下回到重音,这正好是可以用MIDI力度曲线模拟的地方。有一点要注意,Grunge鼓组的过门不像前卫摇滚那么密集,但时常会出现“反常规”的重音摆放,比如过门最后一下不在第4拍上,而在第4拍后半拍,这种细节最容易被量化步骤抹掉,我会在元数据里单独打一个“syncopated_fill”标记。

3.4 量化尺度与人性化保留:什么该量化,什么该手动留

量化是所有扒谱者绕不开的问题。我的原则是:时值位置可以量化,但力度和部分偏移要保留。具体说,底鼓和贝斯这些律动核心量化到16分音符网格,切分音位置也按16分音符对齐;吉他Riff里如果原曲是均匀的8分音符闷音,就量化到8分音符,但如果是那种故意往后拽的推弦进入,就手动把音符起始位置往后拖20到40毫秒,并记录一个“humanize”标记。

刚开始做数据集时我贪图省事,全部统一量化,结果回放时听感特别死板。后来总结出规律:量化解决“对齐”的问题,但解决不了“律动”的问题。Grunge的“脏”很大程度来自乐手之间微小的时值差,如果把这些差异全部抹掉,风格特征就丢了。因此在元数据里我每轨加了一个timing_quantization字段,记录这一轨用了多少毫秒的量化容差,方便后续做风格还原实验时把“量化”和“摇摆”当成可调参数来用,而不是把量化当作不可逆的固定步骤。

4. Python侧的数据处理与特征工程

4.1 用Python解析MIDI并重建轨道结构

MIDI文件导出之后,先用Python解析库把音符事件读进结构化表格,每一行是一个音符,列包含轨道名、起始拍号、音高、力度、时长、控制器事件。解析时我特别注意把DAW默认的音符偏移去掉——有些DAW在导出MIDI时会统一加上一个很小的Tick偏移,如果不归一化,两张轨道本来应该对齐的音符在数值上会差出10个Tick左右,统计上不致命,但做音轨对位就会出问题。

我写了一个对齐函数,输入两轨的音符列表,输出能使重叠率最大化的时移量,用来检查所有轨道是否真的对齐到同一个网格。实际跑下来发现偶尔会有某首鼓轨整体晚了半个16分音符,八成是扒谱时不小心在段落头多插了一个空白小节。这种错误在DAW里播放时几乎察觉不到,因为鼓声本身就会覆盖掉前面的空白,但一旦跟吉他轨做音符级对比,错位立刻现形。所以这个检查函数比人耳可靠得多,我每次批量导出后都会全量跑一遍。

4.2 针对Grunge的Riff特征与风格量化

符号化之后就可以算特征了。我重点算了几组:第一是音高直方图和音高类直方图,用来判断一首歌是否大量停留在E5、A5、D5这些强力和弦根音上;第二是音符密度,按每小节平均音符数分轨统计,节奏吉他的密度通常远高于主音吉他和贝斯;第三是切分音占比,这个指标最能反映Grunge的“推动感”——把每个音符的起始位置映射到16分音符网格,统计落在反拍上的比例;第四是力度标准差,它反映动态落差,A队作品的力量标准差普遍比金属乐高出一截,因为主歌和副歌的力度级差太大。

和弦行程是另一个重点。我从MIDI里还原每小节的和声功能,加上调性信息做归一化,统计各乐队最常使用的和弦邻接对。跑出来的结果很有意思:B队大量使用Ⅰ-Ⅳ-Ⅴ的平直进行,但D队经常出现Ⅵ-Ⅳ-Ⅴ-Ⅰ这种把自然小调和混合利底亚混在一起的走向。这种差异如果只看音频频谱特征根本看不出来,符号化数据的优势就在这——你能精确回答“这支乐队到底用了哪些序列”,而不是停留在“听起来很黑暗”的定性描述上。

4.3 数据集校验脚本:常见错误自查

我写了一个校验脚本放在数据集根目录,每次更新后跑一遍,主要查四类问题。第一是MIDI音高范围,任何低于A0或高于C8的音符都会报警,多半是扒谱时钢琴卷帘窗拖过头了。第二是段落边界一致性,检查音频切片长度和MIDI段落标记是否匹配,误差超过100毫秒就提示复核。第三是轨道重叠检查,同一首歌里不同轨道的同音高音符如果重叠超过60%时长,可能是误把同一乐句扒了两遍。第四是元数据字段完整性,必填字段缺失就列出来。

这个脚本最大的价值不是代替人工校对,而是把重复劳动自动化。手工扒谱时最容易犯的是低级错误,比如某条轨道的力度忘改了、某首BPM小数点后差一位、某个调弦标记漏填,这些都能被脚本秒杀。早期我全靠手工抽查,后来发现低级错误率大概在每首歌三到五处,实在太影响数据集质量了,才下决心把校验脚本补齐。现在每次合并新曲目前跑一遍,十分钟就能完成全量检查。

5. 实操避坑与质量控制

5.1 最难扒的往往是节奏吉他的闷音段

很多人以为主音吉他solo最难扒,实际不是。Solo至少音高清楚、音符稀疏,节奏吉他的密集闷音连复段才是最折磨人的。这类段落的音高信息很少,全部集中在一个或两个音上,差别全在时值上。我的经验是一次只循环一个强拍窗口,开节拍器听三遍,先把重音位置记下来,再补中间的弱音;如果某个16分音符位置始终听不出来,就拿波形看——闷音的波形幅度明显小于放开的音符,而且包络没有明显尖峰。

记住不要开着全曲去反复拖播放头,那只会让耳朵疲劳。我发现集中精力听同一段超过15分钟就会产生听觉疲劳,判断力直线下降,这时候宁可按保存休息十分钟,回来之后往往一遍就通过了。手工扒谱比写代码更吃状态,身体疲劳会导致对音高和时值的判断全面失真,这不是技术问题,是生理规律。

5.2 底鼓与贝斯“波形打架”的对齐办法

Grunge录音的低频段通常混得很满,底鼓和贝斯在同一频段打架,直接看波形很难找到贝斯的起音位置。我会切一个200Hz以下的低通通道出来看,或者直接看频谱图:贝斯的起音通常在底鼓冲击后几毫秒出现,频谱上表现为一个连续的泛音列突然增强。把视窗放大到一格20到50毫秒,就能把两者的时间关系看清楚,这一个技巧能省掉至少一半的贝斯定位时间。

这个问题的另一个解决思路是参考现场版。很多歌的录音室版低频模糊,但现场版的贝斯线条清晰得多。利用同一首歌的现场版来辅助确认贝斯走向,再回到录音室版修正时值,是我现在最常用的双版本校验法。如果某首歌没有现场版素材,那就只能靠低通滤波加频谱图硬啃,效率低一些,但准确度同样能保证。

5.3 扒谱尺度:还原编曲意图,不追求逐音符复制

扒了十几首之后我意识到一个问题:扒谱的目标不是把录音里的每一个泛音、每一个杂音都复制出来,而是还原乐手在编曲层面上的意图。比如录音室专辑里很多节奏吉他轨其实是双轨叠录的,左右声道各弹一遍,合在一起听起来更宽。我标注时会把它还原成一条主干Riff,而不是把两条近似的Riff都写进去,否则后续做乐句统计会被重复信息污染,相似度过高的两条轨道会让音符密度特征失真。

人声部分同理。原唱在主歌里可能有大量即兴的小弯音和气息变化,MIDI不可能也不应该全量还原,我只标主干音高和节奏,细小的装饰音放到装饰音字段里存文本描述。这套“主干优先、装饰单列”的标注尺度,保证了数据在不同曲目间的一致性——如果每首歌的标注粒度不一样,数据集的可用性就会大打折扣。这个原则虽然简单,但我在很多开源数据集里都没看到被贯彻,导致同一条轨道里混着“符号级精度”和“段落级概括”两种粒度,根本没法做统计。

5.4 快速回放校验的A/B工作流

校对这一关,我用的是最笨也最有效的方法:DAW里原曲放一个声道,MIDI回放放另一个声道,用声道切换快捷键做A/B对比,逐段核对。正常情况下每扒完一段就立刻A/B一次,不要攒到整首歌扒完再对,不然后面全错位了对都不知道,排查起来等于重新扒一遍。A/B的节奏一般是先听两遍原曲,再切到MIDI听两遍,第三遍再切回原曲验证差异点,这个循环能保证耳朵保持在新鲜状态。

校验时重点关注两点:一是音符的起始是否在听觉上对齐,而不是视觉上是否贴在网格线上;二是力度关系是否还原了段落的起伏。理论上谱面正确的回放不一定“像”原曲,很多时候就是力度曲线不对——副歌没推上去,整段听起来就是平的。针对这种情况我会在副歌入口手动画一个渐强曲线,让MIDI回放里的情绪起伏和原曲匹配起来,这一步做完,回放听感会立刻上一个台阶。

这个数据集从开始构想到最终校验完,前后断断续续做了三个多月。现在每次回听MIDI回放,还是能明显感觉到B队的Riff比A队“糙”、D队的低音比C队“沉”,这些差异用符号特征量化之后变得更加清晰。我自己后续打算继续扩展两三个邻近风格的数据集做对比,也考虑把扒谱过程中的段落标记与力度曲线一起放出来,让做音乐生成实验的人有更细粒度的监督信号。如果你也在做类似的数据集,我的建议是别怕慢,标注的一致性和元数据的完整度,永远比数量重要。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询