当前位置:  开发笔记 > 编程语言 > 正文

如何正确地将MIDI刻度转换为毫秒?

如何解决《如何正确地将MIDI刻度转换为毫秒?》经验,为你挑选了1个好方法。

我正在尝试将MIDI刻度/增量时间转换为毫秒,并且已经找到了一些有用的资源:

    MIDI Delta时间刻度到秒

    如何将midi时间轴转换为应该播放的实际时间轴

    MIDI时间码规范

    MTC

问题是我认为我没有正确使用这些信息.我试过应用Nik扩展的公式:

[  1 min    60 sec   1 beat     Z clocks ]
| ------- * ------ * -------- * -------- | = seconds
[ X beats   1 min    Y clocks       1    ]

使用此测试MIDI文件中的元数据:




像这样:

self.toSeconds = 60.0 * self.t[0][2].clocks_per_click / (self.t[0][0].tempo * self.t[0][2].denominator) * 10

这最初看起来不错,但随后它似乎漂移了.这是一个使用Mido和pygame的基本可运行示例(假设pygame正确播放):

import threading

import pygame
from pygame.locals import *

from mido import MidiFile,MetaMessage

music_file = "Bee_Gees_-_Stayin_Alive-Voice.mid"

#audio setup
freq = 44100    # audio CD quality
bitsize = -16   # unsigned 16 bit
channels = 2    # 1 is mono, 2 is stereo
buffer = 1024    # number of samples
pygame.mixer.init(freq, bitsize, channels, buffer)
pygame.mixer.music.set_volume(0.8)


class MIDIPlayer(threading.Thread):
    def __init__(self,music_file):
        try:
            #MIDI parsing
            self.mid = MidiFile(music_file)
            self.t = self.mid.tracks

            for i, track in enumerate(self.mid.tracks):
                print('Track {}: {}'.format(i, track.name))
                for message in track:
                    if isinstance(message, MetaMessage):
                        if message.type == 'time_signature' or message.type == 'set_tempo' or message.type == 'key_signature':
                            print message

            self.t0 = self.t[0][3:len(self.t[0])-1]
            self.t0l = len(self.t0)
            self.toSeconds = 60.0 * self.t[0][2].clocks_per_click / (self.t[0][0].tempo * self.t[0][2].denominator) * 10
            print "self.toSeconds",self.toSeconds
            #timing setup
            self.event_id = 0
            self.now = pygame.time.get_ticks()
            self.play_music(music_file)
        except KeyboardInterrupt:
            pygame.mixer.music.fadeout(1000)
            pygame.mixer.music.stop()
            raise SystemExit

    def play_music(self,music_file):
        clock = pygame.time.Clock()
        try:
            pygame.mixer.music.load(music_file)
            print "Music file %s loaded!" % music_file
        except pygame.error:
            print "File %s not found! (%s)" % (music_file, pygame.get_error())
            return
        pygame.mixer.music.play()
        while pygame.mixer.music.get_busy():
            # check if playback has finished
            millis = pygame.time.get_ticks()
            deltaMillis = self.t0[self.event_id].time * self.toSeconds * 1000
            # print millis,deltaMillis
            if millis - self.now >= deltaMillis:
                print self.t0[self.event_id].text
                self.event_id = (self.event_id + 1) % self.t0l
                self.now = millis
            clock.tick(30)

MIDIPlayer(music_file)

上面的代码应该做的是根据midi文件在正确的时间打印正确的歌词,但它会随着时间的推移而漂移.

将MIDI增量时间转换为秒/毫秒的正确方法是什么?

更新

基于CL的有用答案,我已更新代码以使用标题中的ticks_per_beat.由于存在单个set_tempo元消息,因此我始终使用此值:

import threading

import pygame
from pygame.locals import *

from mido import MidiFile,MetaMessage

music_file = "Bee_Gees_-_Stayin_Alive-Voice.mid"

#audio setup
freq = 44100    # audio CD quality
bitsize = -16   # unsigned 16 bit
channels = 2    # 1 is mono, 2 is stereo
buffer = 1024    # number of samples
pygame.mixer.init(freq, bitsize, channels, buffer)
pygame.mixer.music.set_volume(0.8)


class MIDIPlayer(threading.Thread):
    def __init__(self,music_file):
        try:
            #MIDI parsing
            self.mid = MidiFile(music_file)
            self.t = self.mid.tracks

            for i, track in enumerate(self.mid.tracks):
                print('Track {}: {}'.format(i, track.name))
                for message in track:
                    # print message
                    if isinstance(message, MetaMessage):
                        if message.type == 'time_signature' or message.type == 'set_tempo' or message.type == 'key_signature' or message.type == 'ticks_per_beat':
                            print message

            self.t0 = self.t[0][3:len(self.t[0])-1]
            self.t0l = len(self.t0)
            self.toSeconds = 60.0 * self.t[0][2].clocks_per_click / (self.t[0][0].tempo * self.t[0][2].denominator) * 10
            print "self.toSeconds",self.toSeconds

            # append delta delays in milliseconds
            self.delays = []

            tempo = self.t[0][0].tempo
            ticks_per_beat = self.mid.ticks_per_beat

            last_event_ticks = 0
            microseconds = 0

            for event in self.t0:
                delta_ticks = event.time - last_event_ticks
                last_event_ticks = event.time
                delta_microseconds = tempo * delta_ticks / ticks_per_beat
                microseconds += delta_microseconds
                print event.text,microseconds/1000000.0
                self.delays.append(microseconds/1000)

            #timing setup
            self.event_id = 0
            self.now = pygame.time.get_ticks()
            self.play_music(music_file)
        except KeyboardInterrupt:
            pygame.mixer.music.fadeout(1000)
            pygame.mixer.music.stop()
            raise SystemExit

    def play_music(self,music_file):
        clock = pygame.time.Clock()
        try:
            pygame.mixer.music.load(music_file)
            print "Music file %s loaded!" % music_file
        except pygame.error:
            print "File %s not found! (%s)" % (music_file, pygame.get_error())
            return
        pygame.mixer.music.play()
        while pygame.mixer.music.get_busy():
            # check if playback has finished
            millis = pygame.time.get_ticks()
            # deltaMillis = self.t0[self.event_id].time * self.toSeconds * 1000
            deltaMillis = self.delays[self.event_id]
            # print millis,deltaMillis
            if millis - self.now >= deltaMillis:
                print self.t0[self.event_id].text
                self.event_id = (self.event_id + 1) % self.t0l
                self.now = millis
            clock.tick(30)

MIDIPlayer(music_file)

我根据转换为毫秒的时间打印的消息的时间看起来要好得多.然而,几秒钟后它仍然漂移.

我是否正确地将MIDI刻度转换为毫秒并跟踪更新循环中传递的毫秒数?

这是如何进行转换的:self.delays = []

    tempo = self.t[0][0].tempo
    ticks_per_beat = self.mid.ticks_per_beat

    last_event_ticks = 0
    microseconds = 0

    for event in self.t0:
        delta_ticks = event.time - last_event_ticks
        last_event_ticks = event.time
        delta_microseconds = tempo * delta_ticks / ticks_per_beat
        microseconds += delta_microseconds
        print event.text,microseconds/1000000.0
        self.delays.append(microseconds/1000)

这就是在时间过去时检查是否遇到"提示"的方法:

millis = pygame.time.get_ticks()
            deltaMillis = self.delays[self.event_id]
            if millis - self.now >= deltaMillis:
                print self.t0[self.event_id].text
                self.event_id = (self.event_id + 1) % self.t0l
                self.now = millis
            clock.tick(30)

我不确定这个实现是否会错误地将MIDI增量刻度转换为毫秒,错误地检查基于毫秒的延迟是否通过或两者都有.



1> CL...:

首先,您必须合并所有曲目,以确保正确处理速度变化事件.(如果首先将增量时间转换为绝对刻度值,这可能会更容易;否则,只要在另一个轨道的事件之间插入事件,就必须重新计算增量时间.)

然后,您必须为每个事件计算到最后一个事件的相对时间,如下面的伪代码.重要的是计算必须使用相对时间,因为速度可能随时发生变化:

tempo = 500000        # default: 120 BPM
ticks_per_beat = ...  # from the file header

last_event_ticks = 0
microseconds = 0
for each event:
    delta_ticks = event.ticks - last_event_ticks
    last_event_ticks = event.ticks
    delta_microseconds = tempo * delta_ticks / ticks_per_beat
    microseconds += delta_microseconds
    if event is a tempo event:
        tempo = event.new_tempo
    # ... handle event ...

推荐阅读
吻过彩虹的脸_378
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有