Skip to content

Instantly share code, notes, and snippets.

@MaurizioB
Created August 30, 2018 23:20
Show Gist options
  • Save MaurizioB/5ec7dcae48648cb4512558bc8f9d04cf to your computer and use it in GitHub Desktop.
Save MaurizioB/5ec7dcae48648cb4512558bc8f9d04cf to your computer and use it in GitHub Desktop.
Timing of MIDI events in seconds
#!/usr/bin/env python2.7
import sys
import midi
def getTempoMap(pattern, maxTick=None):
#create a first tempo (default to 120, if there is no tempo event)
tempoMap = [[0, .5 / pattern.resolution]]
lastTick = 0
for track in pattern:
for event in track:
if maxTick is not None and event.tick > maxTick:
continue
if isinstance(event, midi.SetTempoEvent):
#set the first tempo event if tick=0
if not event.tick:
tempoMap[0][1] = 60. / pattern.resolution / event.bpm
#set duration of the previous tempo event
else:
tempoMap[-1][0] = event.tick
tempoMap.append([0, 60. / pattern.resolution / event.bpm])
lastTick = max(lastTick, event.tick)
#set duration of last tempo change based on latest event tick
tempoMap[-1][0] = lastTick
return list(sorted(tempoMap, key=lambda e: e[0]))
#return timing of a single event, from a given the pattern,
#using the track and event number
def getEventTime(pattern, trackId, eventId):
relative = pattern.tick_relative
pattern.make_ticks_abs()
maxTick = pattern[trackId][eventId].tick
tempoMap = getTempoMap(pattern, maxTick)
pattern.make_ticks_rel()
tick = time = 0
tempoMap = iter(tempoMap)
nextTick, tickLength = tempoMap.next()
while nextTick < maxTick:
time += (nextTick - tick) * tickLength
tick = nextTick
nextTick, tickLength = tempoMap.next()
#restore previous status
if not relative:
pattern.make_ticks_abs()
#add remaining ticks
return time + (maxTick - tick) * tickLength
#with a given pattern and track number, return a list of (event, time)
def getTrackTimings(pattern, trackId, noteOff=False):
relative = pattern.tick_relative
pattern.make_ticks_abs()
tempoMap = getTempoMap(pattern)
pattern.make_ticks_rel()
eventType = midi.NoteEvent if noteOff else midi.NoteOnEvent
tick = time = 0
tempoMap = iter(tempoMap)
nextTick, tickLength = tempoMap.next()
notes = []
iterEvents = iter(pattern[trackId])
event = iterEvents.next()
while True:
try:
while tick + event.tick < nextTick:
time += event.tick * tickLength
tick += event.tick
if isinstance(event, eventType):
notes.append((event, time))
event = iterEvents.next()
remaining = nextTick - tick
time += remaining * tickLength
tick += remaining
nextTick, tickLength = tempoMap.next()
#check for other tempo changes
while tick + (event.tick - remaining) > nextTick:
remaining = nextTick - tick
time += remaining * tickLength
tick += remaining
nextTick, tickLength = tempoMap.next()
time += (event.tick - remaining) * tickLength
tick += (event.tick - remaining)
if isinstance(event, eventType):
notes.append((event, time))
event = iterEvents.next()
except StopIteration:
if not relative:
pattern.make_ticks_abs()
return notes
getNote = lambda n: ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'][n % 12] + str(n // 12 - 2)
pattern = midi.read_midifile(sys.argv[1])
timings = getTrackTimings(pattern, 1)
for event, time in timings:
print('Note {} plays at {:.02f} seconds'.format(getNote(event.pitch), time))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment