发起:唐里 | 校对:敬爱的勇哥 | 审核:唐里 |
---|---|---|
参与翻译(1)人:路双宁 | ||
原文:Visualizing Musical Performance |
作为一个音乐家兼数据科学家,我对音乐表演可视化的想法很感兴趣。在这篇文章中,我简述了如何使用MAESTRO数据集对钢琴演奏的录音可视化。本文提供了一些例子。下面,我使用Python的Mido,Seaborn和Pandas包,用代码一步一步地说明,打开、清理和可视化MAESTRO数据集中的钢琴表演。文章最后以弗朗兹·李斯特《匈牙利狂想曲第2号》的可视化作结,并录下了这首曲子,让读者可以通过可视化来观看这首曲子的展开。还提供了用于创建可视化的完整Python脚本的链接。
数据
MAESTRO数据集包含超过200小时的国际钢琴电子竞赛的钢琴表演。参赛者使用雅马哈自动演奏钢琴演奏,这种原声钢琴还可以捕捉和回放乐器数字接口(MIDI)数据。MAESTRO数据集包含参赛者演奏的MIDI数据和演奏的录音。
MIDI是一种允许数码乐器通过“消息”相互通信的协议。这些消息存储着用于回放的乐器类型、要播放的音符、音符何时开始、何时结束等信息。
简介:在Python中打开MIDI文件
在Python中打开一个MIDI文件,要安装mido包。从mido包里引入MidiFile。下面的代码是一个在Python中用MidiFile打开一个文件的例子。对于MAESTRO数据,这个过程会创建一个包含两个轨道的MidiFile对象。第一个轨道包含演奏的元数据(例如时间、作品签名)。第二个轨道包含演奏的信息。
1 | from mido import MidiFile |
阐述:处理数据
在本文中,我使用以下步骤来处理数据以创建可视化。
步骤1:提取第二个轨道,通过对轨道进行遍历来提取数据。其结果应该是一个消息对象的list。遍历的时候,忽略第一个和最后一个消息。第一个消息是程序消息,最后一个是元消息,表示轨道的结束。这两条消息都没有提供所播放的音符的数据。
1 | message_list = [] |
步骤2:遍历消息的list,将消息转换成字符串。可以用python的str()函数或者mido包提供的format_as_string()方法完成到字符换的转换。
1 | message_strings = [] |
步骤3:使用split(‘’)把消息字符串分割成包含键和值的子串
1 | message_strings_split = [] |
这将从列表中创造一个列表,单个列表如下所示:
1 | ['control_change', 'channel=0', 'control=64', 'value=43', 'time=0'] |
列表的第一个子字符串只包含一个值(消息类型)。为了将数据转换为字典,必须删除这个子字符串。
步骤4:删除第一个子字符串,将其存储在列表中,并将列表转换为dataframe。
1 | message_type = [] |
步骤5:把列表中的其它子串转换成字典,再转换成dataframe。=号将子串的键和值分隔开。下面的代码遍历列表和每个子字符串,以创建键值对。然后,它将这些键值对存储在字典中(每个子字符串列表对应一个字典)。最后,它将字典转换为一个dataframe。
1 | attributes = [] |
步骤6:将属性(df2)的dataframe与包含消息类型(df1)的dataframe连接起来。
1 | df_complete = pd.concat([df1, df2], axis=1) |
步骤7:dataframe中的time变量表示自上次消息以来经过的时间。note变量表示弹奏的钢琴键。为了绘制数据,需要测量经过的时间来绘制数据随时间的变化。可以使用Python的.cumsum()方法创建time_elapsed变量。绘制时需要将变量从string类型转换为float类型。
1 | # 直接将Nan转化成int会报错,因此先将Nan填充为0 |
步骤8:过滤掉控制消息和note_off消息,因为这些消息对可视化来说不会被用到。控制消息表示何时按下和释放延音踏板与弱音踏板。控制消息的消息类型等于“control”。note_off消息由速度为0的note_on类型的消息表示。
1 | df_filtered = df_complete[df_complete['message_type']=='note_on'] |
步骤9:删除dataframe中不必要的列
1 | df_filtered.drop(['channel', 'value', 'control', 'time'], axis=1, inplace=True) |
步骤10:处理过程的最后一步是分别在dataframe开始和结尾添加一行。添加这些行将在可视化的边缘和数据点之间提供一些空间。新的第一行被分配了音符值0(钢琴的较低范围之外的值),并且经过的时间值等于最大经过时间值的-0.05倍。(第一个note_on消息的time_elapsed值大于0)。新的最后一行被分配了一个音符值127(钢琴上限范围之外的值),经过的时间值等于最大经过时间值的1.5倍。添加这些行可以使生成的可视化具有平滑的边缘。
1 | add_first_row = [] |
更进一步:可视化
为了将演奏可视化,我用了Python的Seaborn包。x轴表示经过的时间(时间从左往右递增),y轴表示弹奏的音符的音高,从低(下)到高(上)。图中显示了使用六边形时,随时间的推移音符的散点图。颜色用来表示六边形中包含的音高范围和一个时间段内的音高频率。在上面的图中,较高的频率用较暗的绿色表示。
散点图显示了六边形所定义的范围内音高的频率,以及这些频率如何随着演奏的进行而变化。其结果是一个可以随着时间(x轴)可视化音高(y轴)和音高频率(颜色)的图。该图包含了所有必要的信息,是所弹奏片段的可视化(参见下面的匈牙利狂想曲示例中的演示)。
下面的步骤详细说明了本文中的可视化是如何绘制的。
步骤1:将Seaborn的样式设置为“white”。这一步提供了一个干净的白色背景,在此基础上构建可视化。
1 | sns.set_style('white') |
步骤2:定义图中x轴和y轴的范围。在第10步处理过程中添加的第一行和最后一行有助于可视化。x轴的范围由经过的最小时间和最大时间来决定。这两个的值都是由额外的行设置的。y轴的范围设置在16和113之间。这样做是为了使图产生平滑的边缘。MIDI音符值(在y轴上绘制)在0到127之间。然而,钢琴只有88个键,它们的MIDI音符值在21到108之间。第一行和最后一行的音符值设置在钢琴的可能值范围之外。因此,通过在第一行和最后一行设置的最小和最大音符值设置y轴范围,可以得到:
- 显示钢琴所有可能的值
- 不显示第一行和最后一行的人工音符值
- 在每个可视化的边缘和最低和最高音符之间有一个空间,并且
- 该空间的颜色与画布的背景相同
关于绘图函数的另外一个注意事项:网格大小表示x方向上六边形的数量。更改网格大小会更改六边形区域的大小,以及随后的图形中六边形的大小。下图绘制的是里姆斯基-科萨柯夫的《野蜂飞舞》。
1 | g = sns.jointplot(df_final.time_elapsed, df_final.note, cmap=palette, |
步骤3:删除图中不需要的元素。为了提供一个艺术性的散点图,我删除了图的边缘、轴线、轴标签和坐标轴(注意:删除这些元素不利于提供一个有意义的可视化)。下面的代码删除了轴线、图边缘、轴标签和刻度标签。
1 | sns.set_style('white') |
概述:
本文介绍了从MAESTRO数据集中打开、清理和可视化MIDI演奏数据的步骤。提供了代码片段来展示这些步骤。利用Seaborn的散点图的方法对演奏的数据进行可视化。这些图显示了音高的密度(或所弹奏音符的频率)随时间的变化。其结果是一种艺术的描绘,把一段音乐捕获在一个单一的图像中。
对于那些有兴趣了解可视化如何与音乐作品的表现相一致的读者,下面是一个例子。这是弗朗兹·李斯特《匈牙利狂想曲第2号》的可视化图。在可视化之后,YouTube上有一段Adam Gyorgy演奏该作品的视频。对于有兴趣创建自己的可视化的读者,可以在GitHub上找到我的脚本和文档。
结尾:匈牙利狂想曲第2号
下图是弗朗兹·李斯特《匈牙利狂想曲第2号》的可视化图以及Adam Gyorgy表演这首曲子的视频。你可以一边听视频,一边通过可视化图感受他的演奏。
视频地址:https://youtu.be/7H99FM6S8rU