Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add a class to convert from absolute MIDI tick to milliseconds #62

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions README.mediawiki
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,23 @@ pattern = midi.read_midifile("example.mid")
print pattern
</pre>

If you want to convert from midi ticks to milliseconds, you can use the TimeResolver
wrapper class:

<pre>
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))
</pre>

==Sequencer==

If you use this toolkit under Linux, you can take advantage of ALSA's
Expand Down
12 changes: 12 additions & 0 deletions examples/example_3.py
Original file line number Diff line number Diff line change
@@ -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))

68 changes: 59 additions & 9 deletions src/sequencer.py
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -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):
Expand All @@ -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):
Expand Down