Python编曲实践(七):整整一百行Python代码写出黑人抬棺梗曲《Astronomia》的旋律

15 篇文章 16 订阅
8 篇文章 7 订阅

前言

黑人抬棺梗如今可以说是火爆全球,而原版视频的背景音乐《Astronomia》也开始爆火,正在不断被油管的音乐播主用各种乐器花式cover:
在这里插入图片描述
受此超级巨梗的影响,本博主也打算跟一跟风,用整整百行Python代码写出这首《Astronomia》的旋律精髓,实现过程使用到了我之前根据 Mido 库的Track类进行扩展而实现的 MidiTrackExtended 类,若您想尝试一下的话可以下载 midi_extended 这个目录并复制到项目目录下,便可以通过import操作将其导入到代码中。请注意,在运行代码前一定要安装好 MidoPyGame 这两个库!

对摇滚史密斯和独立电子感兴趣的朋友们,欢迎关注鄙人B站主页,感谢大家支持!

问题总结

在本博文上线两天期间,受到了很多网友的热心反馈,在此表示诚挚的感谢,也十分感激大家对本篇博文的支持!🤜🤛
根据网友们的反馈,本篇文章中的代码运行不了主要原因有以下几点:

  1. midi_extended文件夹与其中的Python文件没有放到工程目录下,这部分是由于github无法单独下载文件夹带来的麻烦,我现已将该目录上传至CSDN,大家可以点击下载

  2. MidoPyGame 这两个库没有安装好,请务必在运行文件前使用pip指令安装好这两个库,因为MidiExtended类的大部分功能是继承自Mido库的相关类,而PyGame库是播放MIDI文件必需的;

  3. 路径不存在,针对此问题已经对代码进行修改,即使用了更简单的路径形式,不出其他意外的话会在此文件的同一目录下生成MIDI文件coffin_dance.mid;

  4. 不报错但是没有声音输出,这可能是由于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语言在音乐方面的应用十分感兴趣,也致力于实现音乐与代码的更好融合,若本篇博文中的内容或代码运行出现任何问题,欢迎随时与我沟通和交流。感谢您的阅读,希望本专题的文章能够为您提供帮助!

  • 211
    点赞
  • 678
    收藏
    觉得还不错? 一键收藏
  • 79
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 79
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值