Created
August 30, 2018 23:20
-
-
Save MaurizioB/5ec7dcae48648cb4512558bc8f9d04cf to your computer and use it in GitHub Desktop.
Timing of MIDI events in seconds
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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