2015-12-08 24 views
9

私はミリ秒にMIDIが/デルタ時間を刻み変換しようとしていると、すでにいくつかの有用なリソースを発見した:MIDIティックをミリ秒に正しく変換する方法は?

  1. MIDI Delta Time Ticks to Seconds
  2. How to convert midi timeline into the actual timeline that should be played
  3. MIDI Time Code spec
  4. MTC

問題は私がこの情報を正しく使用しているとは思わないことです。

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

this test MIDI fileからメタデータを使用して::

<meta message set_tempo tempo=576923 time=0> 
<meta message key_signature key='Ab' time=0> 
<meta message time_signature numerator=4 denominator=4 clocks_per_click=24 notated_32nd_notes_per_beat=8 time=0> 

をので、同じように:

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

これは、最初はOKに見えますが、それはそうです、私はニックが展開式を適用しようとした ドリフトする。ここ はMidopygameを使用して、基本的な実行可能な例(仮定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デルタ時間を秒/ミリ秒に変換する正しい方法は何ですか?私は、ヘッダーから ticks_per_beat を使用するコードを更新したCLの役に立つ回答に基づいて

更新

。単一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ティックをミリ秒に正しく変換し、ループ中の更新で経過したミリ秒を追跡していますか?

変換が行われるか、この:私は

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) 

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

と、これは時間の経過とともにチェックは 'キュー' が発生した場合にはどのようですこの実装がMIDIデルタティックをミリ秒に間違って変換するかどうか確かではなく、ミリ秒ベースの遅延が過ぎるかどうかを間違ってチェックします。

答えて

5

まず、すべてのトラックをマージして、テンポ変更イベントが正しく処理されるようにする必要があります。デルタ時間を絶対ティック値に変換すると、デルタ時間を最初に変換する方が簡単です(そうでない場合、別のトラックのイベント間にイベントが挿入されるたびにデルタ時間を再計算する必要があります)。

各イベント、次の擬似コードのように、最後のイベントまでの相対時間。テンポがいつでも変更されている可能性があるため、計算では相対時間を使用する必要があります。

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 ... 
+0

ありがとうございました。これをちょっと試してみましょう。ちょうどダブルチェックしたい: '' 'clocks_per_click'''は' '' ticks_per_beat'''と同じですか? (私はメタメッセージに '' 'ticks_per_beat'''を見つけることができないようです) –

+0

イベントの時刻を決定するために拍子のイベントは*必要ではありません。彼らは表記のためだけに有用です。 Midoでは、1ビートあたりのティックを指定するプロパティは 'ticks_per_beat'と呼ばれます。 –

+0

申し訳ありませんが、私は非常にうまく説明したとは思わない。私が使用しているテストMIDIファイルのMIDIメッセージは、テキスト[here](http://lifesine.eu/so/Bee_Gees_-Stayin_Alive-Voice.txt)として表示されています。 '' ticks_per_beat'''と呼ばれるメッセージはありません。 MIDIファイルがこの情報を見逃している可能性はありますか?もしそうなら、何ができますか?ありがとう(+1) –

1

フレームレートを高くしたい場合があります。私のシステムでは、clock.tick(30)からclock.tick(300)に増加すると良い結果が得られます。あなたは、あなたのタイミングがオフになってどれだけ印刷することにより、これを測定することができます:30で

print self.t0[self.event_id].text, millis - self.now - deltaMillis 

は手がかりが背後に20から30ミリ秒遅れているダニ。 300ティックでは、最大で2ミリ秒遅れる。これをさらに増やしたいかもしれません。

stdoutがバッファリングされないようにするには、-uスイッチでpythonを実行する必要があります(行が改行で終わっているので不要です)。

私はタイミングを決定するのに苦労しますが、「ああ、ハッハッハー」から判断すると、これらの変更で正しいと思われます。

+0

OMG!私はとても近かった(まだこれまでのところ)とてもシンプルだ!私のためにそれを分解していただきありがとうございます。私はCLなしではここにいないだろう。あなたの助け –

関連する問題