文章目录
前言
midi是基于事件的通用音乐表现格式,是计算机音乐的基础数据结构。mido为用于解析和生成Midi文件的python库,本文以mido库来生成常见的架子鼓节奏。
一、mido基本操作
官方示例:
from mido import Message, MidiFile, MidiTrack
mid = MidiFile() # 创建一个MidiFile对象
track = MidiTrack() # 创建一个(或多个)MidiTrack对象
mid.tracks.append(track) # 将track添加到MidiFile对象中
track.append(Message('program_change', program=12, time=0)) # 添加Message对象
track.append(Message('note_on', note=64, velocity=64, time=32)) # 添加note_on 事件
track.append(Message('note_off', note=64, velocity=127, time=32)) # 添加note_off 事件
mid.save('new_song.mid') # 保存mid文件
基本概念
message
message:mido提供的消息类,包含很多的属性和方法。
其第一个参数为messagetype,列举最常见的3个:
note_on:音符的开始事件
note_off:音符的结束事件
control_change:更改乐器
参数note:音符的音高。60代表中央C,每增加12,音高升高一个八度
参数velocity:音符的音量,取值在0-127之间
time:音符的演奏时间,这里要重点留意,因为它不是单纯的每个音符的持续时间。
官方解释:In MIDI file tracks, it is used as delta time (in ticks), and it must be a non-negative integer.
意思是它是一个增量时间,代表上一个音符结束后到此音符的间隔时间。这个时间以tick为单位,而在mido的默认配置中,1拍中有480个tick。所以要想生成一个长度为1拍的音符,应该设置其time值为480,而不是1。后面在同时按下多个音符的时候再举例说明
meta message
MetaMessage是mido中另一个重要的类,是用于track的一些基本设置
time_signature是拍号设置,numerator 为一小节几拍,denominator 为几分音符,如3/4的设置为一小节3拍,4分音符为1拍
set_tempo是用于设置音乐的节奏快慢,由于这里tempo的单位不是BPM(Beat Per Minute),故一般配合bpm2tempo来使用
key_signature是用于设置音乐的调式的,C为C大调,若是小调的话仅需要在后面添加小写字母m,如Cm表示C小调
二、架子鼓mido编曲实现
1.基本设置
为了方便操作,定义了一个鼓的演奏类DrusmPlay
from mido import Message, MidiFile, MidiTrack, bpm2tempo, MetaMessage
class DrusmPlay:
def __init__(self, track):
self.bpm = 80
self.track = track
self.tempo = bpm2tempo(self.bpm)
self.meta_time = 480
# 一拍的时间,用于Message中time的值。用480*n来表示
self.track.append(MetaMessage('set_tempo', tempo=self.tempo, time=0))
# 设置节奏快慢
self.track.append(MetaMessage('time_signature', numerator=4, denominator=4))
# 拍号设置
2.midi音色对应表
用一个字典来保存对应鼓的note,后面用**是我个人觉得在架子鼓演奏中较常用的音色
self.drum_dict = {
'acoustic_bass': 35, # 大鼓
'bass1': 36, # 低音鼓 **
'side_stick': 37, # 边击
'acoustic_snare': 38, # 小鼓(松) **
'hand_clap': 39, # 拍手
'electric_snare': 40, # 小鼓(紧)
'low_floor_tom': 41, # 通鼓(最低) **
'closed_hi-hat': 42, # 立镲(闭) **
'high_floor_tom': 43, # 通鼓(低) **
'pedal_hi-hat': 44, # 踩镲
'low_tom': 45, # 通鼓(中低) **
'open_hi-hat': 46, # 立镲(开) **
'low-mid_tom': 47, # 通鼓(中) **
'hi-mid_tom': 48, # 通鼓(中高) **
'crash_cymbal1': 49, # 低砸音镲 **
'high_tom': 50, # 通鼓(高) **
'ride_cymbal1': 51, # 厚吊镲(低) **
'chinese_cymbal': 52, # 锣
'ride_bell': 53, # 厚吊镲(中)
'tambourine': 54, # 铃鼓
'splash_cymbal': 55, # 小吊镲
'cowbell': 56, # 牛铃
'crash_cymbal2': 57, # 薄吊镲(高) 高砸音镲
'vibraslap': 58, # 振音梆盒
'ride_cymbal2': 59, # 厚吊镲(高)
'hi_bongo': 60, # 邦戈鼓(高)
'low_bongo': 61, # 邦戈鼓(低)
'mute_hi_bongo': 62, # 康加鼓(高闭)
'open_hi_bongo': 63, # 康加鼓(高开)
'low_conga': 64, # 康加鼓(低)
'high_timbale': 65, # 边鼓(高)
'low_timbale': 66, # 边鼓(低)
'high_agogo': 67, # 拉丁打铃(高)
'low_agogo': 68, # 拉丁打铃(低)
'cabasa': 69, # 喀吧萨
'maracas': 70, # 沙锤
'short_whistle': 71, # 哨子(短)
'long_whistle': 72, # 哨子(长)
'short_guiro': 73, # 刮板(短)
'long_guiro': 74, # 刮板(长)
'claves': 75, # 响棒
'hi_wood_block': 76, # 梆盒(高)
'low_wood_block': 77, # 梆盒(低)
'mute_cuica': 78, # 拉鼓(闭)
'open_cuica': 79, # 拉鼓(开)
'mute_triangle': 80, # 三角铁(闭)
'open_triangle': 81 # 三角铁(开)
}
3.基本节奏的添加
接下来来实现一个基本的“动次打次”节奏型的演奏,4小节,每小节4拍,8个8分音符来组成:
def dongcidaci(self):
for i in range(4):
# 写4小节
# 第一个八分音符:bass鼓和crash_cymbal同时击下
self.track.append(
Message('note_on', note=self.drum_dict['bass1'], velocity=64, time=0, channel=9))
self.track.append(
Message('note_on', note=self.drum_dict['crash_cymbal1'], velocity=64, time=0, channel=9))
self.track.append(
Message('note_off', note=self.drum_dict['bass1'], velocity=64, time=round(self.meta_time * 0.5),
channel=9))
self.track.append(
Message('note_off', note=self.drum_dict['crash_cymbal1'], velocity=64, time=0, channel=9))
# 留意crash_cymbal1的note_off事件,time是0,代表前一个音符结束也同时结束
# 第二个八分音符:闭镲
self.track.append(
Message('note_on', note=self.drum_dict['closed_hi-hat'], velocity=64, time=0, channel=9))
self.track.append(
Message('note_off', note=self.drum_dict['closed_hi-hat'], velocity=64, time=round(self.meta_time * 0.5),
channel=9))
# 第三个八分音符:军鼓和闭镲同时击下
self.track.append(
Message('note_on', note=self.drum_dict['acoustic_snare'], velocity=64, time=0, channel=9))
self.track.append(
Message('note_on', note=self.drum_dict['closed_hi-hat'], velocity=64, time=0, channel=9))
self.track.append(
Message('note_off', note=self.drum_dict['acoustic_snare'], velocity=64,
time=round(self.meta_time * 0.5), channel=9))
self.track.append(
Message('note_off', note=self.drum_dict['closed_hi-hat'], velocity=64, time=0, channel=9))
# 第四个八分音符:闭镲
self.track.append(
Message('note_on', note=self.drum_dict['closed_hi-hat'], velocity=64, time=0, channel=9))
self.track.append(
Message('note_off', note=self.drum_dict['closed_hi-hat'], velocity=64, time=round(self.meta_time * 0.5),
channel=9))
# 第五个八分音符:bass鼓和closed_hi-hat同时击下
self.track.append(
Message('note_on', note=self.drum_dict['bass1'], velocity=64, time=0, channel=9))
self.track.append(
Message('note_on', note=self.drum_dict['closed_hi-hat'], velocity=64, time=0, channel=9))
self.track.append(
Message('note_off', note=self.drum_dict['bass1'], velocity=64, time=round(self.meta_time * 0.5),
channel=9))
self.track.append(
Message('note_off', note=self.drum_dict['closed_hi-hat'], velocity=64, time=0, channel=9))
# 留意crash_cymbal1的note_off事件,time是0,代表前一个音符结束也同时结束
# 第六个八分音符:闭镲
self.track.append(
Message('note_on', note=self.drum_dict['closed_hi-hat'], velocity=64, time=0, channel=9))
self.track.append(
Message('note_off', note=self.drum_dict['closed_hi-hat'], velocity=64, time=round(self.meta_time * 0.5),
channel=9))
# 第七个八分音符:军鼓和闭镲同时击下
self.track.append(
Message('note_on', note=self.drum_dict['acoustic_snare'], velocity=64, time=0, channel=9))
self.track.append(
Message('note_on', note=self.drum_dict['closed_hi-hat'], velocity=64, time=0, channel=9))
self.track.append(
Message('note_off', note=self.drum_dict['acoustic_snare'], velocity=64,
time=round(self.meta_time * 0.5), channel=9))
self.track.append(
Message('note_off', note=self.drum_dict['closed_hi-hat'], velocity=64, time=0, channel=9))
# 第八个八分音符:闭镲
self.track.append(
Message('note_on', note=self.drum_dict['closed_hi-hat'], velocity=64, time=0, channel=9))
self.track.append(
Message('note_off', note=self.drum_dict['closed_hi-hat'], velocity=64, time=round(self.meta_time * 0.5),
channel=9))
4.生成mid文件
生成并保存mid文件
if __name__ == "__main__":
mid = MidiFile()
track = MidiTrack()
mid.tracks.append(track)
dp = DrusmPlay(track)
dp.dongcidaci()
mid.save('drums.mid')
三、评估
评估方法:用MuseScore写一段group truch的节奏,用于与上面生成的mid进行对比。
musescore打谱
先用musescore打一段4小节4拍的谱:
用musescore对比
用musescore打开刚刚生成的drums.mid文件,会自动转换成曲谱:
可以看到,两段曲谱是一致的。
LMMS对比
也可以通过LMMS软件打开两段mid来对比
四、总结
本文尝试了mido来生成鼓的基本节奏,可以作为计算机混音,编曲,转录的基础。
后续工作:
1.进一步抽象更多的节奏型,简化输入
2.鼓谱转录的相关工作
参考:
https://blog.youkuaiyun.com/TruedickDing/article/details/101780003
https://blog.youkuaiyun.com/TruedickDing/article/details/102014762
https://blog.youkuaiyun.com/weixin_43572492/article/details/84987309
https://blog.youkuaiyun.com/X_i_n_w_e_i/article/details/104434139