为啥干净的 Midi 文件播放不同? (与米多)

Posted

技术标签:

【中文标题】为啥干净的 Midi 文件播放不同? (与米多)【英文标题】:Why is clean Midi file playing differently? (with mido)为什么干净的 Midi 文件播放不同? (与米多) 【发布时间】:2020-01-03 00:38:16 【问题描述】:

我编写了一个程序,它接收一个 MIDI 文件,然后使用 Mido,它通过删除特定类型的元数据、重复消息等来清理数据。它还计算了累积时间(作为每个 MIDI 消息中的时间处于增量时间)。然后使用它创建一个新的 mido 文件(从头开始),我将所有这些消息附加到一个轨道中(因此基本上轨道被合并)并按累积时间对它们进行排序。然后相应地调整增量时间(请记住,每个新的 midi 轨道从累积时间 0 开始)。我意识到这可能看起来毫无意义(因为我正在尝试构建更干净的同一首歌曲),但目的是获得更好的数据然后做其他事情。

我已将我的代码分成两部分。第一个执行所有过滤并构造一个大列表,其中每个子列表中的第一项是消息本身,每个子列表中的第二项是累积时间(如上所述,这是按累积时间排序的)。代码的第二部分调整此列表中每个项目的增量时间,然后将列表中的所有消息按顺序(具有更正的增量时间)附加到从头开始创建的轨道上。然后它使用 pygame 播放该曲目。

我似乎遇到的主要问题是时机/节奏。重建的曲目似乎播放得太快或太慢。在某些文件(例如 Bohemian Rhapsody 文件)的情况下,乐器部分似乎也分离和混乱。

这是解构和列表构建代码:


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()

【问题讨论】:

【参考方案1】:

您需要保存ticks_per_beat。只需使用ticksperbeat = mid.ticks_per_beat 获取原始文件的ticks_per_beat,然后将新文件设置为mid.ticks_per_beat = ticksperbeat

【讨论】:

以上是关于为啥干净的 Midi 文件播放不同? (与米多)的主要内容,如果未能解决你的问题,请参考以下文章

播放多乐器 MIDI 文件

异步播放midi文件?

Flutter mobile,播放midi文件

使用 chrome 播放 MIDI 文件

在 python 中播放 MIDI 文件?

在 python 中播放 MIDI 文件?