人工智能编曲是一个十分复杂的话题,而这一话题的起点便是选择一个良好的编曲媒介,使得开发者能够将AI的音乐灵感记录下来,并且能够很方便地将其播放、编辑、分享。
MIDI文件是电脑编曲的一种通用格式,它容易通过音乐编辑软件导入、导出,也有很多现成的库函数来对其进行编辑加工。
首先,我找到了PythonWiki提供的音乐库合集 - PythonInMusic,在这里上百个库之中,仅有寥寥几个是支持Python3且仍有活力的,在其中Mido和PyGame.midi库是其中比较好用的两个库,本篇文章就采用这两个库来进行MIDI文件的编写和播放
关于用Mido库来创建一个新的MIDI文件,官方文档给出了如下示例代码:
from mido import Message, MidiFile, MidiTrack
mid = MidiFile()
track = MidiTrack()
mid.tracks.append(track)
track.append(Message('program_change', program=12, time=0))
track.append(Message('note_on', note=64, velocity=64, time=32))
track.append(Message('note_off', note=64, velocity=127, time=32))
mid.save('new_song.mid')
这段示例代码虽然短,可是已经将编写MIDI文件的基本思路完全表达出来了:
下面我通过对Message和MetaMessage这两个十分重要的概念的进一步说明,来加深大家理解
Message对象的类型十分复杂,是根据MIDI文件的格式实现的,官方文档有详细列表,在此我们不一一列举,而仅对我使用到的三种Message来进行分析:
program_change是用于更改不同channel的乐器音色的,格式为:
Message('program_change', channel, program, time=0)
note_on消息,可以理解为音符的开始,其格式为
Message('note_on', note, velocity, time, channel)
note_off消息,可以理解为音符的结束,一般紧跟在note_on消息之后,其格式与上面的相同
Message('note_off', note, velocity, time, channel)
MetaMessage的种类也很多,可以参考官方文档,我只使用了3种MetaMessage,列举在下面:
tempo = 75
tempo = mido.bpm2tempo(bpm)
meta_time = MetaMessage('time_signature', numerator=3, denominator=4)
meta_tempo = MetaMessage('set_tempo', tempo = tempo, time=0)
meta_tone = MetaMessage('key_signature', key='C')
由于Message对象需要的参数比较多而且单位转换复杂繁琐,故我自己编写了一个play_note函数来更加方便编曲:
def play_note(note, length, track, base_num=0, delay=0, velocity=1.0, channel=0):
meta_time = 60 * 60 * 10 / bpm
major_notes = [0, 2, 2, 1, 2, 2, 2, 1]
base_note = 60
track.append(Message('note_on', note=base_note + base_num*12 + sum(major_notes[0:note]), velocity=round(64*velocity), time=round(delay*meta_time), channel=channel))
track.append(Message('note_off', note=base_note + base_num*12 + sum(major_notes[0:note]), velocity=round(64*velocity), time=round(meta_time*length), channel=channel))
下面开始正式编曲了,我选择的是《大海啊,故乡》这首歌,简谱如下:
由于我们是纯乐器演奏,而前奏与后面重复率极高,故略过前奏。之后我将此音乐以八小节为单位分为3个部分,其中后两部分仅一个半音部分有区别。根据此特征,我编写了chorus和verse两个函数,代码如下:
def verse(track):
play_note(1, 0.5, track) # 小
play_note(2, 0.5, track) # 时
play_note(1, 1.5, track) # 候
play_note(7, 0.25, track, -1) # 妈
play_note(6, 0.25, track, -1) # 妈
play_note(5, 0.5, track, -1, channel=1) # 对
play_note(3, 0.5, track, channel=1) # 我
play_note(3, 2, track, channel=1) # 讲
play_note(3, 0.5, track) # 大
play_note(4, 0.5, track)
play_note(3, 1.5, track) # 海
play_note(2, 0.25, track) # 就
play_note(1, 0.25, track) # 是
play_note(6, 0.5, track, -1, channel=1) # 我
play_note(2, 0.5, track, channel=1) # 故
play_note(2, 2, track, channel=1) # 乡
play_note(7, 0.5, track, -1) # 海
play_note(1, 0.5, track)
play_note(7, 1.5, track, -1) # 边
play_note(6, 0.25, track, -1)
play_note(5, 0.25, track, -1)
play_note(5, 0.5, track, -1, channel=1) # 出
play_note(2, 0.5, track, channel=1)
play_note(2, 2, track, channel=1) # 生
play_note(4, 1.5, track) # 海
play_note(3, 0.5, track) # 里
play_note(1, 0.5, track) # 成
play_note(6, 0.5, track, -1)
play_note(1, 3, track) # 长
def chorus(track, num):
play_note(5, 0.5, track) # 大
play_note(6, 0.5, track)
play_note(5, 1.5, track) # 海
play_note(3, 0.5, track) # 啊
play_note(5, 0.5, track, channel=1) # 大
play_note(6, 0.5, track, channel=1)
play_note(5, 2, track, channel=1) # 海
play_note(6, 0.5, track) # 是(就)
play_note(5, 0.5, track) # 我(像)
play_note(4, 0.5, track) # 生(妈)
if num == 1:
play_note(1, 0.25, track, channel=1) # 活
play_note(1, 0.25, track, channel=1) # 的
if num == 2:
play_note(1, 0.5, track, channel=1) # (妈)
play_note(6, 0.5, track, channel=1) # 地(一)
play_note(5, 0.5, track, channel=1)
play_note(5, 3, track, channel=1) # 方(样)
play_note(3, 0.5, track) # 海(走)
play_note(4, 0.5, track) # 风(遍)
play_note(3, 1.5, track) # 吹(天)
play_note(2, 0.25, track) # (涯)
play_note(1, 0.25, track)
play_note(6, 0.5, track, -1, channel=1) # 海(海)
play_note(2, 0.5, track, channel=1) # 浪
play_note(2, 2, track, channel=1) # 涌(角)
play_note(4, 0.5, track) # 随(总)
play_note(5, 0.5, track) # 我(在)
play_note(4, 0.5, track) # 漂(我)
play_note(3, 0.5, track) # 流(的)
play_note(1, 0.5, track, channel=1) # 四(身)
play_note(6, 0.5, track, -1, channel=1)
play_note(1, 3, track, channel=1) # 方(旁)
PyGame的midi模块提供了一个很好的播放midi的功能,由于代码非原创,故仅仅贴出这个函数:
def play_midi(file):
freq = 44100
bitsize = -16
channels = 2
buffer = 1024
pygame.mixer.init(freq, bitsize, channels, buffer)
pygame.mixer.music.set_volume(1)
clock = pygame.time.Clock()
try:
pygame.mixer.music.load(file)
except:
import traceback
print(traceback.format_exc())
pygame.mixer.music.play()
while pygame.mixer.music.get_busy():
clock.tick(30)
至此编曲工作已经告一段落,顺便向大家推荐一款免费MIDI播放与编辑软件MidiEditor。虽然没有Pro Tools和Cubase等专业编曲软件的全面功能,但是对于MIDI文件编写的基本需求而言足够了,我们的作品在MidiEditor像这样:
单音轨的音乐听起来还是比较单薄,这篇文章也是我进行智能编曲的尝试和敲门砖,争取之后能够使用更简便的方法做出更复杂更动听的音乐,谢谢关注!
完整工程见 Github