Próbuję przekonwertować MIDI kleszcze/czas delta do milisekund i znalazłem kilka przydatnych zasobów już:Jak poprawnie przekonwertować ticks MIDI na milisekundy?
- MIDI Delta Time Ticks to Seconds
- How to convert midi timeline into the actual timeline that should be played
- MIDI Time Code spec
- 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.
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) –
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". –
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) –