我正在尝试将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增量刻度转换为毫秒,错误地检查基于毫秒的延迟是否通过或两者都有.
首先,您必须合并所有曲目,以确保正确处理速度变化事件.(如果首先将增量时间转换为绝对刻度值,这可能会更容易;否则,只要在另一个轨道的事件之间插入事件,就必须重新计算增量时间.)
然后,您必须为每个事件计算到最后一个事件的相对时间,如下面的伪代码.重要的是计算必须使用相对时间,因为速度可能随时发生变化:
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 ...