From 9aa092e653684c871b9ee2293ee8e640bb1cab34 Mon Sep 17 00:00:00 2001 From: stefaan himpe Date: Fri, 7 Aug 2015 23:23:58 +0200 Subject: [PATCH] add a class to convert from absolute MIDI tick to milliseconds --- README.mediawiki | 17 +++++++++++ examples/example_3.py | 12 ++++++++ src/sequencer.py | 68 +++++++++++++++++++++++++++++++++++++------ 3 files changed, 88 insertions(+), 9 deletions(-) create mode 100644 examples/example_3.py diff --git a/README.mediawiki b/README.mediawiki index 001d754..a9f372d 100644 --- a/README.mediawiki +++ b/README.mediawiki @@ -160,6 +160,23 @@ pattern = midi.read_midifile("example.mid") print pattern +If you want to convert from midi ticks to milliseconds, you can use the TimeResolver +wrapper class: + +
+import midi
+import midi.sequencer as sequencer
+pattern = midi.read_midifile("example.mid")
+pattern.make_ticks_abs()
+time_resolver = sequencer.TimeResolver(pattern)
+for track in pattern:
+    for event in track:
+        name = event.name
+        tick = event.tick
+        milliseconds = time_resolver.tick2ms(tick)
+        print ("event {0} with MIDI tick {1} happens after {2} milliseconds.".format(name, tick, milliseconds))
+
+ ==Sequencer== If you use this toolkit under Linux, you can take advantage of ALSA's diff --git a/examples/example_3.py b/examples/example_3.py new file mode 100644 index 0000000..2363aab --- /dev/null +++ b/examples/example_3.py @@ -0,0 +1,12 @@ +import midi +import midi.sequencer as sequencer + +pattern = midi.read_file("mary.mid") +pattern.make_ticks_abs() +timeresolver = sequencer.TimeResolver(pattern) +for track in pattern: + for event in track: + tick = event.tick + milliseconds = timeresolver.tick2ms(tick) + print ("event {2} at tick {0} happens {1} ms after starting the piece".format(tick, milliseconds, event.name)) + diff --git a/src/sequencer.py b/src/sequencer.py index c7842d1..eb3a159 100644 --- a/src/sequencer.py +++ b/src/sequencer.py @@ -1,6 +1,6 @@ class TempoMap(list): - def __init__(self, stream): - self.stream = stream + def __init__(self, resolution): + self.resolution = resolution def add_and_update(self, event): self.add(event) @@ -12,7 +12,7 @@ def add(self, event): # convert into milliseconds per beat tempo = tempo / 1000.0 # generate ms per tick - event.mpt = tempo / self.stream.resolution + event.mpt = tempo / self.resolution self.append(event) def update(self): @@ -26,12 +26,62 @@ def update(self): last = event def get_tempo(self, offset=0): - last = self[0] - for tm in self[1:]: - if tm.tick > offset: - return last - last = tm - return last + try: + last = self[0] + for tm in self[1:]: + if tm.tick > offset: + return last + last = tm + return last + except IndexError: + # no tempo changes specified in midi track + last = SetTempoEvent() + last.bpm = 120 + last.mpqn = 500 + last.mpt = last.mpqn / self.resolution + self.append(last) + return last + +class TimeResolver(object): + """ + iterates over a pattern and analyzes timing information + the result of the analysis can be used to convert from absolute midi tick to wall clock time (in milliseconds). + """ + def __init__(self, pattern): + self.pattern = pattern + self.tempomap = TempoMap(self.pattern.resolution) + self.__resolve_timing() + + def __resolve_timing(self): + """ + go over all events and initialize a tempo map + """ + # backup original mode and turn to absolute + original_ticks_relative = self.pattern.tick_relative + self.pattern.make_ticks_abs() + # create a tempo map + self.__init_tempomap() + # restore original mode + if (original_ticks_relative): + self.pattern.make_ticks_rel() + + def __init_tempomap(self): + """ + initialize the tempo map which tracks tempo changes through time + """ + for track in self.pattern: + for event in track: + if event.name == "Set Tempo": + self.tempomap.add(event) + self.tempomap.update() + + def tick2ms(self, absolute_tick): + """ + convert absolute midi tick to wall clock time (milliseconds) + """ + ev = self.tempomap.get_tempo(absolute_tick) + ms = ev.msdelay + ((absolute_tick - ev.tick)*ev.mpt) + return ms class EventStreamIterator(object): def __init__(self, stream, window):