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):