为什么干净的 Midi 文件播放不同? (带美度)
Why is clean Midi file playing differently? (with mido)
我编写了一个接收 MIDI 文件的程序,然后使用 Mido,它通过删除特定类型的元数据、重复消息等来清理数据。它还计算了累积时间(作为每个midi 消息是增量时间)。然后用它来创建一个新的 mido 文件(从头开始),我在其中将所有这些消息附加到一个轨道中(因此基本上轨道被合并)并按累积时间对它们进行排序。然后相应地调整增量时间(请记住,每个新的 MIDI 轨道都从累积时间 0 开始)。我意识到这可能看起来毫无意义(因为我正在尝试构建更清晰的同一首歌),但目的是获得更好的数据,然后用它来做其他事情。
我已将我的代码分成两部分。第一个进行所有过滤并构建一个大列表列表,其中每个子列表中的第一项是消息本身,每个子列表中的第二项是累积时间(如上所述,这是按累积时间排序的).代码的第二部分调整此列表中每个项目的增量时间,然后按顺序(使用更正的增量时间)将列表中的所有消息附加到从头创建的轨道上。然后它使用 pygame 播放此曲目。
我似乎遇到的主要问题是 timing/tempo。重建的曲目似乎播放得太快或太慢。在某些文件(例如波西米亚狂想曲文件)的情况下,器乐部分似乎也分离和混乱。
这是解构和列表构建代码:
import mido
import pygame
all_mid = ['major-scale.mid']
# check is midi file is type 2 (and removes if so) - this is unlikely but can happen on old sites
def remove_type_2(midi):
return True if midi.type == 2 else False
# removes unnecessary meta data types
def filter_meta_type(msg):
accept = ["set_tempo", "time_signature", "key_signature"]
return True if msg.type in accept else False
# removes tempo duplicates and only keeps the last tempo stated for a particular cumulative time
def remove_extra_tempo(msg, msgwithtempos, current_time):
if not msgwithtempos: # if the list is empty
msgwithtempos.append([msg, current_time])
else:
for i in range(len(msgwithtempos)):
msgwithtempo = msgwithtempos[i]
if msgwithtempo[1] == current_time: # this checks duplicates
msgwithtempos.remove(msgwithtempo)
msgwithtempos.append([msg, current_time])
return msgwithtempos
def do_shit(mid, all_messages): # for each track (then message) do the following
msgwithtempos = []
for i, track in enumerate(mid.tracks):
current_time = 0
print(f"Track {i}: {track.name}")
for msg in track:
current_time += msg.time
if msg.type == "sysex data":
pass
elif msg.is_meta:
if filter_meta_type(msg):
if msg.type == "set_tempo":
msgwithtempos = remove_extra_tempo(msg, msgwithtempos, current_time)
else:
all_messages.append([msg, current_time])
else:
all_messages.append([msg, current_time])
return all_messages, msgwithtempos
def main(): # for each midi file do the following
all_lists = []
for i in range(0, len(all_mid)):
all_messages = []
mid = mido.MidiFile(all_mid[i])
if not remove_type_2(mid):
all_messages, msgwithtempos = do_shit(mid, all_messages)
final_messages = all_messages + msgwithtempos
final_messages = sorted(final_messages, key=lambda x: x[1])
all_lists.append(final_messages)
print(all_lists)
return all_lists
if __name__ == '__main__':
main()
这是重构代码:
import mido
import pygame
import regulate_tracks
def play_with_pygame(song):
pygame.init()
pygame.mixer.music.load(song)
length = pygame.time.get_ticks()
pygame.mixer.music.play()
while pygame.mixer.music.get_busy():
pygame.time.wait(length)
def printmessages(mid):
for i, track in enumerate(mid.tracks):
print(f"Track {i}: {track.name}")
for msg in track:
print(msg)
def main():
# get the list of midi files from regulate_tracks
output = regulate_tracks.main()
list1 = output[0]
# create a blank midi file and add a track to it
mid = mido.MidiFile()
track = mido.MidiTrack()
mid.tracks.append(track)
for i in range(len(list1)):
message = list1[i][0]
print(message.type)
if i == 0:
message.time = 0
else:
message.time = list1[i][1] - list1[i - 1][1]
print(message)
track.append(message)
mid.save('new_song.mid')
printmessages(mid)
play_with_pygame('new_song.mid')
if __name__ == '__main__':
main()
示例文件:
波西米亚狂想曲:https://bitmidi.com/queen-bohemian-rhapsody-mid
河流在你体内流动:http://midicollection.net/songs/index.php?id=13
谢谢
编辑:
这似乎是空白 midi 创建和附加的问题,因为我创建了这段代码,该代码从一个短的 midi 文件中手动复制消息,但在播放时它们仍然听起来错误(较慢)。
import mido
import pygame
def play_with_pygame(song):
pygame.init()
pygame.mixer.music.load(song)
length = pygame.time.get_ticks()
pygame.mixer.music.play()
while pygame.mixer.music.get_busy():
pygame.time.wait(length)
def main():
mid = mido.MidiFile()
track = mido.MidiTrack()
mid.tracks.append(track)
track.append(mido.MetaMessage('set_tempo', tempo=500000, time=3840))
track.append(mido.MetaMessage('end_of_track', time=0))
track = mido.MidiTrack()
mid.tracks.append(track)
track.append(mido.Message('note_on', channel=0, note=60, velocity=100, time=0))
track.append(mido.Message('note_on', channel=0, note=62, velocity=100, time=960))
track.append(mido.Message('note_on', channel=0, note=64, velocity=100, time=960))
track.append(mido.Message('note_on', channel=0, note=65, velocity=100, time=960))
track.append(mido.Message('program_change', channel=0, program=123, time=960))
track.append(mido.Message('note_on', channel=0, note=67, velocity=100, time=0))
track.append(mido.Message('note_on', channel=0, note=69, velocity=100, time=960))
track.append(mido.Message('note_on', channel=0, note=71, velocity=100, time=960))
track.append(mido.Message('note_on', channel=0, note=72, velocity=100, time=960))
track.append(mido.Message('note_off', channel=0, note=60, velocity=100, time=2880))
track.append(mido.Message('note_off', channel=0, note=62, velocity=100, time=960))
track.append(mido.Message('note_off', channel=0, note=64, velocity=100, time=960))
track.append(mido.Message('note_off', channel=0, note=65, velocity=100, time=960))
track.append(mido.Message('note_off', channel=0, note=67, velocity=100, time=960))
track.append(mido.Message('note_off', channel=0, note=69, velocity=100, time=960))
track.append(mido.Message('note_off', channel=0, note=71, velocity=100, time=960))
track.append(mido.Message('note_off', channel=0, note=72, velocity=100, time=960))
track.append(mido.MetaMessage('end_of_track', time=0))
mid.save('new_song.mid')
play_with_pygame('new_song.mid')
if __name__ == '__main__':
main()
您需要保存 ticks_per_beat
。只需用 ticksperbeat = mid.ticks_per_beat
获取原始文件的 ticks_per_beat
,然后将新文件设置为 mid.ticks_per_beat = ticksperbeat
.
我编写了一个接收 MIDI 文件的程序,然后使用 Mido,它通过删除特定类型的元数据、重复消息等来清理数据。它还计算了累积时间(作为每个midi 消息是增量时间)。然后用它来创建一个新的 mido 文件(从头开始),我在其中将所有这些消息附加到一个轨道中(因此基本上轨道被合并)并按累积时间对它们进行排序。然后相应地调整增量时间(请记住,每个新的 MIDI 轨道都从累积时间 0 开始)。我意识到这可能看起来毫无意义(因为我正在尝试构建更清晰的同一首歌),但目的是获得更好的数据,然后用它来做其他事情。
我已将我的代码分成两部分。第一个进行所有过滤并构建一个大列表列表,其中每个子列表中的第一项是消息本身,每个子列表中的第二项是累积时间(如上所述,这是按累积时间排序的).代码的第二部分调整此列表中每个项目的增量时间,然后按顺序(使用更正的增量时间)将列表中的所有消息附加到从头创建的轨道上。然后它使用 pygame 播放此曲目。
我似乎遇到的主要问题是 timing/tempo。重建的曲目似乎播放得太快或太慢。在某些文件(例如波西米亚狂想曲文件)的情况下,器乐部分似乎也分离和混乱。
这是解构和列表构建代码:
import mido
import pygame
all_mid = ['major-scale.mid']
# check is midi file is type 2 (and removes if so) - this is unlikely but can happen on old sites
def remove_type_2(midi):
return True if midi.type == 2 else False
# removes unnecessary meta data types
def filter_meta_type(msg):
accept = ["set_tempo", "time_signature", "key_signature"]
return True if msg.type in accept else False
# removes tempo duplicates and only keeps the last tempo stated for a particular cumulative time
def remove_extra_tempo(msg, msgwithtempos, current_time):
if not msgwithtempos: # if the list is empty
msgwithtempos.append([msg, current_time])
else:
for i in range(len(msgwithtempos)):
msgwithtempo = msgwithtempos[i]
if msgwithtempo[1] == current_time: # this checks duplicates
msgwithtempos.remove(msgwithtempo)
msgwithtempos.append([msg, current_time])
return msgwithtempos
def do_shit(mid, all_messages): # for each track (then message) do the following
msgwithtempos = []
for i, track in enumerate(mid.tracks):
current_time = 0
print(f"Track {i}: {track.name}")
for msg in track:
current_time += msg.time
if msg.type == "sysex data":
pass
elif msg.is_meta:
if filter_meta_type(msg):
if msg.type == "set_tempo":
msgwithtempos = remove_extra_tempo(msg, msgwithtempos, current_time)
else:
all_messages.append([msg, current_time])
else:
all_messages.append([msg, current_time])
return all_messages, msgwithtempos
def main(): # for each midi file do the following
all_lists = []
for i in range(0, len(all_mid)):
all_messages = []
mid = mido.MidiFile(all_mid[i])
if not remove_type_2(mid):
all_messages, msgwithtempos = do_shit(mid, all_messages)
final_messages = all_messages + msgwithtempos
final_messages = sorted(final_messages, key=lambda x: x[1])
all_lists.append(final_messages)
print(all_lists)
return all_lists
if __name__ == '__main__':
main()
这是重构代码:
import mido
import pygame
import regulate_tracks
def play_with_pygame(song):
pygame.init()
pygame.mixer.music.load(song)
length = pygame.time.get_ticks()
pygame.mixer.music.play()
while pygame.mixer.music.get_busy():
pygame.time.wait(length)
def printmessages(mid):
for i, track in enumerate(mid.tracks):
print(f"Track {i}: {track.name}")
for msg in track:
print(msg)
def main():
# get the list of midi files from regulate_tracks
output = regulate_tracks.main()
list1 = output[0]
# create a blank midi file and add a track to it
mid = mido.MidiFile()
track = mido.MidiTrack()
mid.tracks.append(track)
for i in range(len(list1)):
message = list1[i][0]
print(message.type)
if i == 0:
message.time = 0
else:
message.time = list1[i][1] - list1[i - 1][1]
print(message)
track.append(message)
mid.save('new_song.mid')
printmessages(mid)
play_with_pygame('new_song.mid')
if __name__ == '__main__':
main()
示例文件:
波西米亚狂想曲:https://bitmidi.com/queen-bohemian-rhapsody-mid
河流在你体内流动:http://midicollection.net/songs/index.php?id=13
谢谢
编辑:
这似乎是空白 midi 创建和附加的问题,因为我创建了这段代码,该代码从一个短的 midi 文件中手动复制消息,但在播放时它们仍然听起来错误(较慢)。
import mido
import pygame
def play_with_pygame(song):
pygame.init()
pygame.mixer.music.load(song)
length = pygame.time.get_ticks()
pygame.mixer.music.play()
while pygame.mixer.music.get_busy():
pygame.time.wait(length)
def main():
mid = mido.MidiFile()
track = mido.MidiTrack()
mid.tracks.append(track)
track.append(mido.MetaMessage('set_tempo', tempo=500000, time=3840))
track.append(mido.MetaMessage('end_of_track', time=0))
track = mido.MidiTrack()
mid.tracks.append(track)
track.append(mido.Message('note_on', channel=0, note=60, velocity=100, time=0))
track.append(mido.Message('note_on', channel=0, note=62, velocity=100, time=960))
track.append(mido.Message('note_on', channel=0, note=64, velocity=100, time=960))
track.append(mido.Message('note_on', channel=0, note=65, velocity=100, time=960))
track.append(mido.Message('program_change', channel=0, program=123, time=960))
track.append(mido.Message('note_on', channel=0, note=67, velocity=100, time=0))
track.append(mido.Message('note_on', channel=0, note=69, velocity=100, time=960))
track.append(mido.Message('note_on', channel=0, note=71, velocity=100, time=960))
track.append(mido.Message('note_on', channel=0, note=72, velocity=100, time=960))
track.append(mido.Message('note_off', channel=0, note=60, velocity=100, time=2880))
track.append(mido.Message('note_off', channel=0, note=62, velocity=100, time=960))
track.append(mido.Message('note_off', channel=0, note=64, velocity=100, time=960))
track.append(mido.Message('note_off', channel=0, note=65, velocity=100, time=960))
track.append(mido.Message('note_off', channel=0, note=67, velocity=100, time=960))
track.append(mido.Message('note_off', channel=0, note=69, velocity=100, time=960))
track.append(mido.Message('note_off', channel=0, note=71, velocity=100, time=960))
track.append(mido.Message('note_off', channel=0, note=72, velocity=100, time=960))
track.append(mido.MetaMessage('end_of_track', time=0))
mid.save('new_song.mid')
play_with_pygame('new_song.mid')
if __name__ == '__main__':
main()
您需要保存 ticks_per_beat
。只需用 ticksperbeat = mid.ticks_per_beat
获取原始文件的 ticks_per_beat
,然后将新文件设置为 mid.ticks_per_beat = ticksperbeat
.