2015-12-08 26 views
9

Próbuję przekonwertować MIDI kleszcze/czas delta do milisekund i znalazłem kilka przydatnych zasobów już:Jak poprawnie przekonwertować ticks MIDI na milisekundy?

  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

problem polega na tym, że nie sądzę, że używam tych informacji poprawnie. Próbowałem stosując wzór Nik rozszerzony:

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

przy użyciu metadanych z 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> 

tak:

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

Ten początkowo wygląda ok, ale to wydaje dryfować. Oto prosty przykład uruchamialny używając Mido i pygame (zakładając pygame odtwarza poprawnie):

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) 

Co powyższy kod powinien zrobić, to wydrukować odpowiednią liryczny w odpowiednim czasie w oparciu o plik MIDI, jednak dryfuje z biegiem czasu.

Jaki jest prawidłowy sposób konwersji czasu delta MIDI na sekundy/milisekundy?

Aktualizacja

podstawie pomocne odpowiedzi CL I został uaktualniony kod do korzystania ticks_per_beat z nagłówka. Ponieważ nie jest pojedynczym set_tempo meta wiadomość, Używam tej wartości w całym:

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) 

taktowanie komunikatów ja drukowania na podstawie czasu przekształca się w milisekundach wygląda o wiele lepiej. Jednak po kilku sekundach nadal dryfuje.

Czy poprawnie konwertuję ticks MIDI na milisekundy i śledzę upłynięcia milisekund w aktualizacji podczas pętli?

jak ta konwersja jest wykonany: 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) 

i to, w jaki sposób sprawdzić, czy 'cue' wystąpił wraz z upływem czasu:

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) 

I "nie jestem pewien, czy ta implementacja niepoprawnie przekształca tickety MIDI na milisekundy, niepoprawnie sprawdza, czy opóźnienia w oparciu o milisekundy kończą się, czy jedno i drugie.

Odpowiedz

5

Najpierw należy scalić wszystkie ścieżki, aby upewnić się, że zdarzenia zmiany tempa są poprawnie przetwarzane.(Prawdopodobnie jest to łatwiejsze, jeśli najpierw przekonwertujesz czasy delta na wartości bezwzględne, w przeciwnym razie będziesz musiał przeliczyć czasy delta za każdym razem, gdy zdarzenie zostanie wstawione pomiędzy wydarzeniami z innej ścieżki.)

Następnie musisz obliczyć, dla każde zdarzenie, względny czas do ostatniego zdarzenia, jak w poniższym kodzie pseudokodowym. Ważne jest, aby obliczenia musiały wykorzystywać czasy względne, ponieważ tempo mogło się zmienić w dowolnej chwili:

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

Dziękuję za odpowiedź. Spróbuję tego trochę. Chciałem tylko sprawdzić: czy '' clocks_per_click''' jest identyczne z '' ticks_per_beat'''? (Nie mogę znaleźć "' ticks_per_beat''' w wiadomościach meta) –

+0

Zdarzenia sygnatur czasowych są * nie * potrzebne do określenia czasu zdarzeń; są przydatne tylko do zapisu. W Mido właściwość, która określa liczbę znaczników na beat, nazywa się "ticks_per_beat". –

+0

Przepraszam, nie sądzę, że wyjaśniłem bardzo dobrze. Możesz zobaczyć komunikaty MIDI testowego pliku MIDI, którego używam jako tekstu [tutaj] (http://lifesine.eu/so/Bee_Gees_-_Stayin_Alive-Voice.txt). Wygląda na to, że nie ma żadnej wiadomości o nazwie '' ticks_per_beat'''. Czy plik MIDI może ominąć te informacje? Jeśli tak, to co można zrobić? Dziękujemy (+1) –

1

Być może chcesz zwiększyć liczbę klatek na sekundę. W moim systemie zwiększenie clock.tick(30) do clock.tick(300) daje dobre wyniki. Można mierzyć to przez drukowanie, ile czasu jest wyłączone:

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

Z 30 kleszczy replik pozostają w 20 do 30 milisekund tyłu. Przy 300 tykach są one najwyżej o 2 milisekundy. Możesz zwiększyć to jeszcze bardziej.

Dla bezpieczeństwa powinieneś uruchomić pythona przełącznikiem -u, aby zapobiec buforowaniu stdout (może to być niepotrzebne, ponieważ linie kończą się znakiem nowej linii).

Trudno mi określić czas, ale sądząc po "Ah ha ha ha", wydaje się, że poprawne są te zmiany.

+0

OMG! Byłem tak blisko (jeszcze tak daleko) Tak prosty! Dzięki, że mi to rozbicie. Nie dostałbym się tutaj bez CL. i twoja pomoc –