黑人抬棺梗如今可以说是火爆全球,而原版视频的背景音乐《Astronomia》也开始爆火,正在不断被油管的音乐播主用各种乐器花式cover:
受此超级巨梗的影响,本博主也打算跟一跟风,用整整百行Python代码写出这首《Astronomia》的旋律精髓,实现过程使用到了我之前根据 Mido 库的Track类进行扩展而实现的 MidiTrackExtended 类,若您想尝试一下的话可以下载 midi_extended 这个目录并复制到项目目录下,便可以通过import操作将其导入到代码中。请注意,在运行代码前一定要安装好 Mido 和 PyGame 这两个库!
在本博文上线两天期间,受到了很多网友的热心反馈,在此表示诚挚的感谢,也十分感激大家对本篇博文的支持!
根据网友们的反馈,本篇文章中的代码运行不了主要原因有以下几点:
midi_extended文件夹与其中的Python文件没有放到工程目录下,这部分是由于github无法单独下载文件夹带来的麻烦,我现已将该目录上传至CSDN,大家可以点击下载;
Mido 和 PyGame 这两个库没有安装好,请务必在运行文件前使用pip指令安装好这两个库,因为MidiExtended类的大部分功能是继承自Mido库的相关类,而PyGame库是播放MIDI文件必需的;
路径不存在,针对此问题已经对代码进行修改,即使用了更简单的路径形式,不出其他意外的话会在此文件的同一目录下生成MIDI文件coffin_dance.mid;
不报错但是没有声音输出,这可能是由于PyGame本身的设置出现了问题,请根据具体的运行环境进行配置。
若出现其他问题,或者这四点问题之中的哪一点无法有效解决,请立即私信联系我!代码的事情不能耽搁!
下面是完整的百行代码,已经上传在了Github上,若您是急性子可以直接拿来尝试咯!
from midi_extended.MidiFileExtended import MidiFileExtended
class CoffinDance():
def __init__(self):
self.bpm = 120
self.time_signature = '4/4'
self.key = 'C'
self.file_path = './coffin_dance.mid'
self.mid = MidiFileExtended(self.file_path, type=1, mode='w')
def write_coffin(self):
self.mid.add_new_track('Lead', self.time_signature, self.bpm, self.key, {'0': 81})
self.intro()
self.verse()
self.verse()
self.verse()
self.end()
def intro(self):
track_lead = self.mid.get_extended_track('Lead')
track_lead.add_meta_info()
for i in range(16):
track_lead.add_note(6, 0.125)
for i in range(8):
track_lead.add_note(1, 0.125, base_num=1)
for i in range(4):
track_lead.add_note(6, 0.125)
for i in range(4):
track_lead.add_note(3, 0.125, base_num=1)
for i in range(4):
track_lead.add_note(2, 0.125, base_num=1)
for i in range(4):
track_lead.add_note(5, 0.125, base_num=1)
for i in range(12):
track_lead.add_note(6, 0.125, base_num=1)
track_lead.add_note(2, 0.125, base_num=1)
track_lead.add_note(1, 0.125, base_num=1)
track_lead.add_note(7, 0.125)
track_lead.add_note(5, 0.125)
def verse(self):
track_lead = self.mid.get_extended_track('Lead')
track_lead.add_note(6, 0.125, base_num=-1)
track_lead.wait(0.125)
track_lead.add_note(6, 0.125, base_num=-1)
track_lead.add_note(3, 0.125)
track_lead.add_note(2, 0.125)
track_lead.wait(0.125)
track_lead.add_note(1, 0.125)
track_lead.wait(0.125)
track_lead.add_note(7, 0.125, -1)
track_lead.wait(0.125)
track_lead.add_note(7, 0.125, -1)
track_lead.add_note(7, 0.125, -1)
track_lead.add_note(2, 0.125)
track_lead.wait(0.125)
track_lead.add_note(1, 0.125)
track_lead.add_note(7, 0.125, -1)
for i in range(2):
track_lead.add_note(6, 0.125, base_num=-1)
track_lead.wait(0.125)
track_lead.add_note(6, 0.125, base_num=-1)
track_lead.add_note(1, 0.125, base_num=1)
track_lead.add_note(7, 0.125)
track_lead.add_note(1, 0.125, base_num=1)
track_lead.add_note(7, 0.125)
track_lead.add_note(1, 0.125, base_num=1)
def end(self):
track_lead = self.mid.get_extended_track('Lead')
for i in range(4):
track_lead.add_note(1, 0.125)
for i in range(4):
track_lead.add_note(3, 0.125)
for i in range(4):
track_lead.add_note(2, 0.125)
for i in range(4):
track_lead.add_note(5, 0.125)
for i in range(12):
track_lead.add_note(6, 0.125)
track_lead.add_note(2, 0.125)
track_lead.add_note(1, 0.125)
track_lead.add_note(7, 0.125, base_num=-1)
track_lead.add_note(6, 0.125, base_num=-1)
track_lead.add_note(6, 0.125, base_num=-1)
track_lead.wait(7.875)
if __name__ == '__main__':
coffin = CoffinDance()
coffin.write_coffin()
coffin.mid.save_midi()
coffin.mid.play_it()
开始前,如果大家对这一梗曲旋律还不太熟悉的话,可以参考一下来自B站的这个原版视频。把这一洗脑旋律刻在脑海里,我们就可以在神秘的非洲力量的帮助下实现这一超级梗曲的编写!
黑人抬棺原版视频
着手写代码之前,我们需要为我们的编曲工作找到曲谱来作为参考,我在Musescore网站上顺利找到了一份单声部的、简单到位的曲谱,其提供了五线谱形式如下:
这一曲谱是单声部的,而且没有时间重叠的音符,而且调式为C大调,这些都为我们的实现提供了许多方便。如果大家看不懂五线谱也没关系,我们只需要从这个图片中知道其中每个标记对应的音高就可以了:
由于这段音乐是C大调的,所以C D E F G A B的排列就相当于简谱中的1234567。其他不多聊,让我们开始一步一步写代码吧!
在加入音符之前,我们需要为文件确定基本的元信息,即速度(120BPM)、节拍信息(4/4拍)和调性信息(C大调),同时确定MIDI文件的保存地址,以及我们要使用的MidiFileExtended类:
class CoffinDance():
def __init__(self):
self.bpm = 120
self.time_signature = '4/4'
self.key = 'C'
self.file_path = './coffin_dance.mid'
self.mid = MidiFileExtended(self.file_path, type=1, mode='w')
之后,我们写一个write函数来确定我们创作的整体结构,即首先创建一个新Track,将其默认乐器设置为电音效果的锯齿波(81号乐器,完整乐器表可以参考此处),之后通过intro、verse和end函数来完成该音乐的三个部分:前奏、副歌和尾奏,其中副歌部分是同样的旋律重复了三次,故通过一个函数来重复调用三次。
def write_coffin(self):
self.mid.add_new_track('Lead', self.time_signature, self.bpm, self.key, {'0': 81})
self.intro()
self.verse()
self.verse()
self.verse()
self.end()
下面我们首先开始实现前奏函数intro,前奏对应的是前10小节的音乐,其中第2到第6小节的音乐太过枯燥,我们将其长度缩短了一点:
def intro(self):
track_lead = self.mid.get_extended_track('Lead')
track_lead.add_meta_info()
for i in range(16):
track_lead.add_note(6, 0.125)
for i in range(8):
track_lead.add_note(1, 0.125, base_num=1)
for i in range(4):
track_lead.add_note(6, 0.125)
for i in range(4):
track_lead.add_note(3, 0.125, base_num=1)
for i in range(4):
track_lead.add_note(2, 0.125, base_num=1)
for i in range(4):
track_lead.add_note(5, 0.125, base_num=1)
for i in range(12):
track_lead.add_note(6, 0.125, base_num=1)
track_lead.add_note(2, 0.125, base_num=1)
track_lead.add_note(1, 0.125, base_num=1)
track_lead.add_note(7, 0.125)
track_lead.add_note(5, 0.125)
第一行是通过get_extended_track函数根据track名得到了我们用于添加音符的音轨,并通过add_meta_info函数将元数据添加到该音轨之中。之后反复使用的add_note函数可以用于向track中添加音符,它前两个参数分别代表音高(简谱中的1234567)和时长(与全音符相比的时长,此处0.125表示8分音符),base_num用于确定音高所在的八度,其中正1代表升高一个八度,负1代表降低一个八度等等。该函数额外的参数可以用于实现弯音轮、滑音和颤音的效果,感兴趣的话可以参考我之前的博文Python编曲实践(三):如何模拟“弯音轮”实现滑音和颤音效果,本篇文章不会用到这些功能,故不作过多介绍。
副歌部分是这个梗曲的精华所在,包括11小节到22小节的内容。我们用verse函数来实现它。为了清晰我将这些乐符按照所在的不同小节而通过换行区分开:
def verse(self):
track_lead = self.mid.get_extended_track('Lead')
track_lead.add_note(6, 0.125, base_num=-1)
track_lead.wait(0.125)
track_lead.add_note(6, 0.125, base_num=-1)
track_lead.add_note(3, 0.125)
track_lead.add_note(2, 0.125)
track_lead.wait(0.125)
track_lead.add_note(1, 0.125)
track_lead.wait(0.125)
track_lead.add_note(7, 0.125, -1)
track_lead.wait(0.125)
track_lead.add_note(7, 0.125, -1)
track_lead.add_note(7, 0.125, -1)
track_lead.add_note(2, 0.125)
track_lead.wait(0.125)
track_lead.add_note(1, 0.125)
track_lead.add_note(7, 0.125, -1)
for i in range(2):
track_lead.add_note(6, 0.125, base_num=-1)
track_lead.wait(0.125)
track_lead.add_note(6, 0.125, base_num=-1)
track_lead.add_note(1, 0.125, base_num=1)
track_lead.add_note(7, 0.125)
track_lead.add_note(1, 0.125, base_num=1)
track_lead.add_note(7, 0.125)
track_lead.add_note(1, 0.125, base_num=1)
代码中的wait函数用于向函数中添加休止符,其参数为休止符的时长。
尾奏部分同前两个函数实现方法一致,也是通过add_note和wait两个函数来添加音符和休止符,在这里不赘述。
def end(self):
track_lead = self.mid.get_extended_track('Lead')
for i in range(4):
track_lead.add_note(1, 0.125)
for i in range(4):
track_lead.add_note(3, 0.125)
for i in range(4):
track_lead.add_note(2, 0.125)
for i in range(4):
track_lead.add_note(5, 0.125)
for i in range(12):
track_lead.add_note(6, 0.125)
track_lead.add_note(2, 0.125)
track_lead.add_note(1, 0.125)
track_lead.add_note(7, 0.125, base_num=-1)
track_lead.add_note(6, 0.125, base_num=-1)
track_lead.add_note(6, 0.125, base_num=-1)
track_lead.wait(7.875)
通过主函数,我们创建了一个CoffinDance对象,通过调用write_coffin函数实现了音乐的创作,调用save_midi函数则会将创作好的文件以MIDI格式保存在file_path下,而play_it函数是调用了pygame的MIDI播放功能,可以将将我们刚刚完成的《Astronomia》梗曲播放出来,在运行前请注意音量大小!
if __name__ == '__main__':
coffin = CoffinDance()
coffin.write_coffin()
coffin.mid.save_midi()
coffin.mid.play_it()
音乐生成后,我们可以通过 MidiEditor 这一个免费、轻量的MIDI编辑软件来查看、编辑或播放这一梗曲,下图是生成的音乐在MidiEditor下的显示情况:
注意:最后送大家一套2020最新企业Pyhon项目实战视频教程,点击此处 进来获取 跟着练习下,希望大家一起进步哦!
若您对Python编曲的其他知识感兴趣,欢迎参考本专题下的其他文章:
Python编曲实践(一):通过Mido和PyGame来编写和播放单轨MIDI文件
Python编曲实践(二):和弦的实现和进行
Python编曲实践(三):如何模拟“弯音轮”实现滑音和颤音效果
Python编曲实践(四):向MIDI文件中添加鼓组音轨
Python编曲实践(五):通过编写爬虫来爬取海量MIDI文件,预备构建数据集(附有百度云下载链接)
Python编曲实践(六):将MIDI文件转化成矩阵,继承PyTorch的Dataset类来构建数据集(附数据集网盘下载链接)
笔者对Python语言在音乐方面的应用十分感兴趣,也致力于实现音乐与代码的更好融合,若本篇博文中的内容或代码运行出现任何问题,欢迎随时与我沟通和交流。感谢您的阅读,希望本专题的文章能够为您提供帮助!