-
Notifications
You must be signed in to change notification settings - Fork 373
Expand file tree
/
Copy pathsequencer.py
More file actions
148 lines (135 loc) · 4.7 KB
/
sequencer.py
File metadata and controls
148 lines (135 loc) · 4.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
class TempoMap(list):
def __init__(self, resolution):
self.resolution = resolution
def add_and_update(self, event):
self.add(event)
self.update()
def add(self, event):
# get tempo in microseconds per beat
tempo = event.mpqn
# convert into milliseconds per beat
tempo = tempo / 1000.0
# generate ms per tick
event.mpt = tempo / self.resolution
self.append(event)
def update(self):
self.sort()
# adjust running time
last = None
for event in self:
if last:
event.msdelay = last.msdelay + \
int(last.mpt * (event.tick - last.tick))
last = event
def get_tempo(self, offset=0):
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):
self.stream = stream
self.trackpool = stream.trackpool
self.window_length = window
self.window_edge = 0
self.leftover = None
self.events = self.stream.iterevents()
# First, need to look ahead to see when the
# tempo markers end
self.ttpts = []
for tempo in stream.tempomap[1:]:
self.ttpts.append(tempo.tick)
# Finally, add the end of track tick.
self.ttpts.append(stream.endoftrack.tick)
self.ttpts = iter(self.ttpts)
# Setup next tempo timepoint
self.ttp = next(self.ttpts)
self.tempomap = iter(self.stream.tempomap)
self.tempo = next(self.tempomap)
self.endoftrack = False
def __iter__(self):
return self
def __next_edge(self):
if self.endoftrack:
raise StopIteration
lastedge = self.window_edge
self.window_edge += int(self.window_length / self.tempo.mpt)
if self.window_edge > self.ttp:
# We're past the tempo-marker.
oldttp = self.ttp
try:
self.ttp = next(self.ttpts)
except StopIteration:
# End of Track!
self.window_edge = self.ttp
self.endoftrack = True
return
# Calculate the next window edge, taking into
# account the tempo change.
msused = (oldttp - lastedge) * self.tempo.mpt
msleft = self.window_length - msused
self.tempo = next(self.tempomap)
ticksleft = msleft / self.tempo.mpt
self.window_edge = ticksleft + self.tempo.tick
def __next__(self):
ret = []
self.__next_edge()
if self.leftover:
if self.leftover.tick > self.window_edge:
return ret
ret.append(self.leftover)
self.leftover = None
for event in self.events:
if event.tick > self.window_edge:
self.leftover = event
return ret
ret.append(event)
return ret