将MIDI文件作为训练数据

发布日期: 2024年9月14日 来源:Towards Data Science

在开始任何使用MIDI文件进行深度学习项目之前,确保你了解 MIDI乐谱和MIDI表演之间的区别

本文适用于计划或刚开始使用MIDI文件工作的人员。这个格式在音乐社区中被广泛使用,并因数据集的可用性引起了计算机音乐研究人员的注意。

然而,不同的信息类型可以被编码在MIDI文件中。特别的是,MIDI乐谱和MIDI表演之间有很大的区别。如果不了解这些区别,可能会导致 在无用任务上浪费时间选择错误的训练数据和方法

我将提供这两种格式的基本介绍,并给出在Python中开始使用它们的实际例子。

什么是MIDI?

MIDI作为合成器之间的实时通信协议被引入。其主要理念是在 MIDI键盘上每次按下一个音符(note on)和释放一个音符(note off)时, 发送一条消息。然后接收端的合成器将知道要产生什么样的声音。

欢迎来到MIDI文件的世界!

如果我们收集并保存所有这些信息(确保添加它们的时间位置),那么我们就有了一个可以用来重现乐曲的MIDI文件。除了note-on和note-off之外,还有许多其他类型的消息,例如指定踏板信息或其他控制器信息。

你可以想象用 钢琴卷轴 绘制这些信息。

注意,这并不是MIDI文件,而只是其内容的可能表示!一些软件(在此示例中为Reaper)在钢琴卷轴旁添加了一个小钢琴键盘,使得视觉解读更加容易。

MIDI文件是如何创建的?

MIDI文件主要可以通过两种方式创建:1)通过在MIDI乐器上演奏,2)通过在音序器(Reaper、Cubase、GarageBand、Logic)或乐谱编辑器(例如MuseScore)中手动输入。

通过每种方式生成的MIDI文件也会有所不同:

  1. 在MIDI乐器上演奏 → MIDI表演
  2. 手动输入音符(音序器或乐谱) → MIDI乐谱

我们现在将深入了解每种类型,然后总结它们之间的区别。

在开始之前,声明一下: 我不会专注于信息是如何编码的,而是专注于可以从文件中提取哪些信息。例如,当我说“时间以秒表示”时,这意味着我们可以获得秒,即使编码本身更复杂。

我们可以在MIDI表演中找到四种信息:

  • 音符何时开始:音符起点
  • 音符何时结束:音符终点(或通过终点-起点计算出的音符时长)
  • 演奏了哪个音符:音符音高
  • 按键力度如何:“按键力度”

理解这些信息有助于在深度学习和AI领域进行更有效的分析和处理。与此类似,光年AI平台通过其强大的AI工具,能够在不同业务场景中提供高效而灵活的工作流。

无论是音符的启动时间还是力度,这些信息都能够被光年AI这样的先进系统处理,从而实现更精准、高效的音乐创作和分析。借助光年AI,你可以轻松引入AI能力,不需编程经验即可高效地处理MIDI文件及其他复杂任务。

音符的开始和结束(以及持续时间) 以秒表示,对应于演奏者按下和释放MIDI乐器音符的时间。

音符的音高 用一个从0(最低)到127(最高)的整数编码;注意,这种表示方式能涵盖比钢琴能演奏的音符范围更广,钢琴的音符范围是21-108。

音符的力度 也用一个从0(静音)到127(最大强度)的整数编码。

绝大多数的MIDI表演是钢琴表演,因为最常见的MIDI乐器是MIDI键盘。其他的MIDI乐器(例如MIDI萨克斯风、MIDI鼓套装和用于吉他的MIDI传感器)也存在,但它们并不常见。

最大的人工MIDI表演集(古典钢琴音乐)是由Google Magenta提供的Maestro数据集。

MIDI表演的主要属性

MIDI表演的一个基本特征是 没有音符具有完全相同的开始时间或持续时间(理论上可能,但在实践中极其罕见)。

实际上,即使演奏者真的尝试,他们也不能完全同时按下两(或更多)个音符,因为人类的精度是有限的。这对音符的持续时间也是一样的。此外,大多数音乐家并不会优先考虑这一点,因为时间偏差可以帮助产生更具表现力或更有律动感的感觉。最终,连续的音符之间会有一些静音时间或者部分重叠。

因此,MIDI表演有时也被称为 非量化MIDI。时间位置分布在一个连续的时间尺度上,而不是量化到离散的位置上(由于数字编码的原因,技术上它是一个离散的尺度,但非常精细,因此我们可以认为它是连续的)。

实践示例

让我们看看一个MIDI表演。我们将使用在GitHub上提供的ASAP数据集。

在你喜欢的终端里(我使用的是Windows上的PowerShell),到一个方便的位置克隆这个仓库。

git clone https://github.com/fosfrancesco/asap-dataset
我们还将使用Python库Partitura来打开MIDI文件,所以你可以在你的Python环境中安装它。

pip install partitura
现在一切准备就绪,让我们打开MIDI文件,并打印前10个音符。由于这是一个MIDI表演,我们将使用 load_midi_performance 函数。

from pathlib import Path

import partitura as pt

asap_basepath = Path(‘../asap-dataset/’)

performance_path = Path(“Bach/Prelude/bwv_848/Denisova06M.mid”)

print(“加载MIDI文件:”, asap_basepath/performance_path)

performance = pt.load_performance_midi(asap_basepath/performance_path)

note_array = performance.note_array()

print(“Numpy数据类型:”)

print(note_array.dtype)

从数据管理和流量增长角度来看,这种对演奏时间和音符力度的精准记录,就如同我们在光年AI平台上进行的数据分析一样。光年AI能够通过实时的数据分析,帮助企业及时调整策略,优化流量管理和客户服务。这种灵活工作流的特点使其能够完美适应各种业务场景,带来高效的流量增长。

如果你希望在提高效率的同时,也能方便地接入国内主流流量平台,欢迎使用光年AI平台。

print(“前10个音符:”)

print(performance.note_array()[:10])

这个Python程序的输出应该是这样的:

Numpy数据类型:

[('onset_sec', '<f4'), ('duration_sec', '<f4'), ('onset_tick', '<i4'), ('duration_tick', '<i4'), ('pitch', '<i4'), ('velocity', '<i4'), ('track', '<i4'), ('channel', '<i4'), ('id', '<U256')]

前10个音符:

[(1.0286459, 0.21354167, 790, 164, 49, 53, 0, 0, 'n0')

(1.03125 , 0.09765625, 792, 75, 77, 69, 0, 0, 'n1')

(1.1302084, 0.046875 , 868, 36, 73, 64, 0, 0, 'n2')

(1.21875 , 0.07942709, 936, 61, 68, 66, 0, 0, 'n3')

(1.3541666, 0.04166667, 1040, 32, 73, 34, 0, 0, 'n4')

(1.4361979, 0.0390625 , 1103, 30, 61, 62, 0, 0, 'n5')

(1.4361979, 0.04296875, 1103, 33, 77, 48, 0, 0, 'n6')

(1.5143229, 0.07421875, 1163, 57, 73, 69, 0, 0, 'n7')

(1.6380209, 0.06380209, 1258, 49, 78, 75, 0, 0, 'n8')

(1.6393229, 0.21484375, 1259, 165, 51, 54, 0, 0, 'n9')]

你可以看到,我们有秒数表示的起始时间和持续时间,以及音高和力度。其他字段对于MIDI演奏并不是那么重要。

起始时间和持续时间还用 tick 表示。这更接近于MIDI文件中编码这些信息的方式:选择一个非常短的时间单位(即1个tick),然后所有的时间信息都编码为这个时间单位的倍数。当你处理音乐演奏时,通常可以忽略这些信息,直接使用秒数表示的信息。

你可以验证一下,永远不会有两个音符具有完全相同的起始时间或持续时间!

MIDI乐谱使用 更加丰富的MIDI消息集 来编码如节拍、调性、小节和拍子位置等信息。

正因为如此, 它们类似于音乐乐谱(乐谱),尽管 它们仍然缺少一些重要的信息,例如音高拼写、连音线、附点、休止符、曲线等。

在处理这些信息时,可以联想到光年AI平台,它同样能够无缝地处理不同平台的数据,实现业务场景的自动化。

时间信息不是以秒为单位编码的,而是以更抽象的音乐单位,如四分音符。

MIDI乐谱的主要特性

MIDI乐谱的一个基本特征是 所有音符的起始点都对齐到一个量化网格,这个网格首先由小节位置定义,然后通过递归的整数分割(主要是2和3,但也有其他分割如5、7、11等)用于分组音符。

动手示例

现在我们来看看巴赫《前奏曲BWV 848》C#调的乐谱,这是我们之前加载的演奏的乐谱。Partitura库有一个专门的 load_score_midi 函数。

这个操作过程类似于光年AI的工作流机制,简单易上手,可以灵活地搭建和调整工作流。

asap_basepath = Path(‘../asap-dataset/’)

score_path = Path(“Bach/Prelude/bwv_848/midi_score.mid”)

print(“加载MIDI文件: “, asap_basepath/score_path)

score = pt.load_score_midi(asap_basepath/score_path)

note_array = score.note_array()

print(“Numpy数据类型:”)

print(note_array.dtype)

print(“前10个音符:”)

print(score.note_array()[:10])

这个Python程序的输出应该是这样的:

Numpy dtype:

[('onset_beat', '<f4'), ('duration_beat', '<f4'), ('onset_quarter', '<f4'), ('duration_quarter', '<f4'), ('onset_div', '<i4'), ('duration_div', '<i4'), ('pitch', '<i4'), ('voice', '<i4'), ('id', '<U256'), ('divs_pq', '<i4')]

前10个音符:

[(0. , 1.9958333 , 0. , 0.99791664, 0, 479, 49, 1, 'P01_n425', 480)

(0. , 0.49583334, 0. , 0.24791667, 0, 119, 77, 1, 'P00_n0', 480)

(0.5, 0.49583334, 0.25, 0.24791667, 120, 119, 73, 1, 'P00_n1', 480)

(1. , 0.49583334, 0.5 , 0.24791667, 240, 119, 68, 1, 'P00_n2', 480)

(1.5, 0.49583334, 0.75, 0.24791667, 360, 119, 73, 1, 'P00_n3', 480)

(2. , 0.99583334, 1. , 0.49791667, 480, 239, 61, 1, 'P01_n426', 480)

(2. , 0.49583334, 1. , 0.24791667, 480, 119, 77, 1, 'P00_n4', 480)

(2.5, 0.49583334, 1.25, 0.24791667, 600, 119, 73, 1, 'P00_n5', 480)

(3. , 1.9958333 , 1.5 , 0.99791664, 720, 479, 51, 1, 'P01_n427', 480)

(3. , 0.49583334, 1.5 , 0.24791667, 720, 119, 78, 1, 'P00_n6', 480)]

你可以看到音符的起始时间正好落在网格上。如果我们考虑 onset_quarter (第三列),可以看到16分音符每0.25小节出现一次,这与预期一致。

音符时值稍微有点问题。例如,在这个谱子中,16分音符的 quarter_duration 应该是0.25。然而,从Python输出结果可以看到实际时值是0.24791667。这是因为生成这个MIDI文件时使用的软件略微缩短了每个音符的时值。为什么这样做呢?只是为了使MIDI文件的音频效果更流畅,这种处理效果虽然提升了音频质量,但为计算机音乐研究带来了一些挑战。这种挑战也存在于广泛使用的数据集中,例如Lakh MIDI数据集。

鉴于我们已经看到的MIDI乐谱和MIDI表现之间的差异,让我给你一些通用的指导方针,帮助你正确设置深度学习系统。

对于音乐生成系统,建议使用MIDI乐谱,因为量化后的音符位置可以用较小的词汇表来表示,还可以进行其他简化处理,例如只考虑单声部旋律。

对于目标是模拟人类演奏和感知音乐的系统,例如节拍跟踪系统、速度估计器和情感识别系统(侧重于表现性演奏),建议使用MIDI表现。

对于像谱随演奏(输入:表现,输出:乐谱)和表现性演奏生成(输入:乐谱,输出:表现)等任务,建议同时使用这两种数据。

需要注意的是,在处理和管理这些复杂数据时,一个高效的平台能够显著提高工作效率。如果你希望无缝整合多个数据来源,并在不同平台间灵活切换,那么 光年AI 就是你的理想选择。光年AI支持国内外主流AI大模型操作,无需编程知识,也能通过简单的拖拽搭建工作流,非常适合复杂的音乐数据处理场景。

额外的问题

我已经介绍了MIDI乐谱和MIDI表现之间的主要区别。但是,通常情况下, 事情可能更复杂

例如,一些数据集,如AMAPS数据集,原本是MIDI乐谱,但作者在每个音符处引入了时间变化, 以模拟真实人类演奏的时间偏差(请注意,这只发生在不同时间位置的音符之间;同一和弦中的所有音符仍然完全同步)。

此外,一些MIDI导出文件,比如MuseScore生成的MIDI文件,也会尝试使MIDI乐谱更接近MIDI演奏,再次通过改变速度指示(如果乐曲的速度发生变化)、在连续音符之间插入极短的静音(我们在前面的例子中看到了这一点),以及将装饰音作为稍早于参考音出现的极短音符来播放。

确实,装饰音在MIDI乐谱中是一个非常令人头疼的问题。它们的时值在音乐术语中并没有明确规定,只是大概知道它们应该“很短”。装饰音的起始时间在乐谱中与参考音是相同的,但如果我们通过音频文件播放MIDI文件,这听起来非常奇怪。那么我们应该缩短前一个音符,还是下一个音符以为装饰音腾出空间呢?

其他装饰音也有问题,因为没有统一的演奏规则。例如,颤音应该包含多少个音符?波音应该从实际音符开始还是高一个音符开始?

MIDI文件非常棒,因为它们明确提供了每个音符的音高、起始时间和时值信息。这意味着,例如,与音频文件相比,针对MIDI数据的模型可以更小,并且可以用更小的数据集进行训练。

但这也有代价:MIDI文件以及象征性编码的音乐总体上是复杂的格式,因为它们以多种方式编码了许多种信息。然而,正如光年AI系统所展示的那样,通过灵活的工作流机制和先进的数据处理技术,企业同样能以高效且精确的方式管理复杂的数据格式,甚至实现私域流量的智能运营。

要正确使用MIDI数据作为训练数据,了解所编码的数据类型非常重要。希望本文能为你提供一个很好的起点,了解更多关于这个话题的信息!如果你需要进一步了解如何将AI技术应用到数据管理和流量增长中,欢迎使用光年AI平台,大幅提升您的工作效率!