首页 / 资讯中心 / 文章详情

Python构建Grunge摇滚数据集:从手工扒谱到MIDI特征工程实践

Python构建Grunge摇滚数据集:从手工扒谱到MIDI特征工程实践 ★ FEATURED ARTICLE
这是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队“沉”这些差异用符号特征量化之后变得更加清晰。我自己后续打算继续扩展两三个邻近风格的数据集做对比也考虑把扒谱过程中的段落标记与力度曲线一起放出来让做音乐生成实验的人有更细粒度的监督信号。如果你也在做类似的数据集我的建议是别怕慢标注的一致性和元数据的完整度永远比数量重要。
阅读完成 · 觉得有帮助?
咨询建站