title: How to correctly convert MIDI ticks to milliseconds?
body: <p>I'm trying to convert MIDI ticks/delta time to milliseconds and have found a few helpful resources already:</p>
  
  <ol>
  <li><a href="http://www.lastrayofhope.co.uk/2009/12/23/midi-delta-time-ticks-to-seconds/2/" rel="nofollow noreferrer">MIDI Delta Time Ticks to Seconds</a></li>
  <li><a href="https://stackoverflow.com/questions/5288593/how-to-convert-midi-timeline-into-the-actual-timeline-that-should-be-played/5297236#5297236">How to convert midi timeline into the actual timeline that should be played</a></li>
  <li><a href="http://web.archive.org/web/20110629053759/http://web.media.mit.edu/~meyers/mcgill/multimedia/senior_project/MTC.html" rel="nofollow noreferrer">MIDI Time Code spec</a></li>
  <li><a href="http://web.archive.org/web/20111211004923/http://home.roadrunner.com/~jgglatt/tech/mtc.htm" rel="nofollow noreferrer">MTC</a></li>
  </ol>
  
  <p>The problem is I don't think I'm using this information correctly.
  I've tried applying the formula Nik expanded:</p>
  
  <pre><code>[  1 min    60 sec   1 beat     Z clocks ]
  | ------- * ------ * -------- * -------- | = seconds
  [ X beats   1 min    Y clocks       1    ]
  </code></pre>
  
  <p>using the metadata from <a href="http://lifesine.eu/so/Bee_Gees_-_Stayin_Alive-Voice.mid" rel="nofollow noreferrer">this test MIDI file</a>:</p>
  
  <pre><code>&lt;meta message set_tempo tempo=576923 time=0&gt;
  &lt;meta message key_signature key='Ab' time=0&gt;
  &lt;meta message time_signature numerator=4 denominator=4 clocks_per_click=24 notated_32nd_notes_per_beat=8 time=0&gt;
  </code></pre>
  
  <p>Like so:</p>
  
  <pre><code>self.toSeconds = 60.0 * self.t[0][2].clocks_per_click / (self.t[0][0].tempo * self.t[0][2].denominator) * 10
  </code></pre>
  
  <p>This initially looks ok, but then it seems to drift.
  Here is a basic runnable example using <a href="https://mido.readthedocs.org/en/latest/midi_files.html#meta-messages" rel="nofollow noreferrer">Mido</a> and <a href="http://www.pygame.org/docs" rel="nofollow noreferrer">pygame</a> (assuming pygame plays back correctly):</p>
  
  <pre><code>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 &gt;= 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)
  </code></pre>
  
  <p>What the above code should do is print the correct lyric at the correct time based on the midi file, yet it drifts over time.</p>
  
  <p>What's the correct way of converting MIDI delta time to seconds/milliseconds ?</p>
  
  <p><strong>Update</strong></p>
  
  <p>Based on CL's helpful answer I've updated the code to use <strong>ticks_per_beat</strong> from the header. Since there is a single <code>set_tempo</code> meta message, I am using this value throughout:</p>
  
  <pre><code>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 &gt;= 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)
  </code></pre>
  
  <p>The timing of the messages I print based on the time converted to milliseconds looks much better. However, after a few seconds it still drifts. </p>
  
  <p>Am I correctly converting MIDI ticks to milliseconds and keep track of passed milliseconds in the update while loop ?</p>
  
  <p>This how the conversion is made:
          self.delays = []</p>
  
  <pre><code>    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)
  </code></pre>
  
  <p>and this is how the check if a 'cue' was encountered as time passes:</p>
  
  <pre><code>millis = pygame.time.get_ticks()
              deltaMillis = self.delays[self.event_id]
              if millis - self.now &gt;= deltaMillis:
                  print self.t0[self.event_id].text
                  self.event_id = (self.event_id + 1) % self.t0l
                  self.now = millis
              clock.tick(30)
  </code></pre>
  
  <p>I'm not sure if this implementation converts MIDI delta ticks to milliseconds incorrectly, incorrectly check if millisecond based delays pass or both.</p>