Skip to content

Commit fd49eb6

Browse files
committed
add a class to convert from absolute MIDI tick to milliseconds
1 parent abb8502 commit fd49eb6

File tree

3 files changed

+88
-9
lines changed

3 files changed

+88
-9
lines changed

README.mediawiki

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,23 @@ pattern = midi.read_midifile("example.mid")
158158
print pattern
159159
</pre>
160160

161+
If you want to convert from midi ticks to milliseconds, you can use the TimeResolver
162+
wrapper class:
163+
164+
<pre>
165+
import midi
166+
import midi.sequencer as sequencer
167+
pattern = midi.read_midifile("example.mid")
168+
pattern.make_ticks_abs()
169+
time_resolver = sequencer.TimeResolver(pattern)
170+
for track in pattern:
171+
for event in track:
172+
name = event.name
173+
tick = event.tick
174+
milliseconds = time_resolver.tick2ms(tick)
175+
print ("event {0} with MIDI tick {1} happens after {2} milliseconds.".format(name, tick, milliseconds))
176+
</pre>
177+
161178
==Sequencer==
162179

163180
If you use this toolkit under Linux, you can take advantage of ALSA's

examples/example_3.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import midi
2+
import midi.sequencer as sequencer
3+
4+
pattern = midi.read_file("mary.mid")
5+
pattern.make_ticks_abs()
6+
timeresolver = sequencer.TimeResolver(pattern)
7+
for track in pattern:
8+
for event in track:
9+
tick = event.tick
10+
milliseconds = timeresolver.tick2ms(tick)
11+
print ("event {2} at tick {0} happens {1} ms after starting the piece".format(tick, milliseconds, event.name))
12+

src/sequencer.py

Lines changed: 59 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
class TempoMap(list):
2-
def __init__(self, stream):
3-
self.stream = stream
2+
def __init__(self, resolution):
3+
self.resolution = resolution
44

55
def add_and_update(self, event):
66
self.add(event)
@@ -12,7 +12,7 @@ def add(self, event):
1212
# convert into milliseconds per beat
1313
tempo = tempo / 1000.0
1414
# generate ms per tick
15-
event.mpt = tempo / self.stream.resolution
15+
event.mpt = tempo / self.resolution
1616
self.append(event)
1717

1818
def update(self):
@@ -26,12 +26,62 @@ def update(self):
2626
last = event
2727

2828
def get_tempo(self, offset=0):
29-
last = self[0]
30-
for tm in self[1:]:
31-
if tm.tick > offset:
32-
return last
33-
last = tm
34-
return last
29+
try:
30+
last = self[0]
31+
for tm in self[1:]:
32+
if tm.tick > offset:
33+
return last
34+
last = tm
35+
return last
36+
except IndexError:
37+
# no tempo changes specified in midi track
38+
last = SetTempoEvent()
39+
last.bpm = 120
40+
last.mpqn = 500
41+
last.mpt = last.mpqn / self.resolution
42+
self.append(last)
43+
return last
44+
45+
class TimeResolver(object):
46+
"""
47+
iterates over a pattern and analyzes timing information
48+
the result of the analysis can be used to convert from absolute midi tick to wall clock time (in milliseconds).
49+
"""
50+
def __init__(self, pattern):
51+
self.pattern = pattern
52+
self.tempomap = TempoMap(self.pattern.resolution)
53+
self.__resolve_timing()
54+
55+
def __resolve_timing(self):
56+
"""
57+
go over all events and initialize a tempo map
58+
"""
59+
# backup original mode and turn to absolute
60+
original_ticks_relative = self.pattern.tick_relative
61+
self.pattern.make_ticks_abs()
62+
# create a tempo map
63+
self.__init_tempomap()
64+
# restore original mode
65+
if (original_ticks_relative):
66+
self.pattern.make_ticks_rel()
67+
68+
def __init_tempomap(self):
69+
"""
70+
initialize the tempo map which tracks tempo changes through time
71+
"""
72+
for track in self.pattern:
73+
for event in track:
74+
if event.name == "Set Tempo":
75+
self.tempomap.add(event)
76+
self.tempomap.update()
77+
78+
def tick2ms(self, absolute_tick):
79+
"""
80+
convert absolute midi tick to wall clock time (milliseconds)
81+
"""
82+
ev = self.tempomap.get_tempo(absolute_tick)
83+
ms = ev.msdelay + ((absolute_tick - ev.tick)*ev.mpt)
84+
return ms
3585

3686
class EventStreamIterator(object):
3787
def __init__(self, stream, window):

0 commit comments

Comments
 (0)