1 """Slice midi streams up.
2
3 Utilities for handling portions of a midi stream in various ways.
4
5 """
6 """
7 Copyright 2011 Giles Hall, Mark Granroth-Wilding
8
9 This file is part of Pymidi2.
10
11 Pymidi2 is free software: you can redistribute it and/or modify
12 it under the terms of the GNU General Public License as published by
13 the Free Software Foundation, either version 3 of the License, or
14 (at your option) any later version.
15
16 Pymidi2 is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 GNU General Public License for more details.
20
21 You should have received a copy of the GNU General Public License
22 along with Pymidi2. If not, see <http://www.gnu.org/licenses/>.
23
24 """
25
26 from midi import *
27 from copy import deepcopy
28
30 """
31 Represents a portion of a midi stream (an L{EventStream}).
32
33 The slice is not actually performed (events cut out of the stream)
34 until needed, e.g. when creating a new event stream from the
35 slice.
36
37 When the slice is performed, events are copied from the original
38 stream into a whole new stream. The original stream is left intact
39 and the events of the new stream are new events.
40
41 """
42 - def __init__(self, stream, start, end=None):
43 """
44 @type stream: L{EventStream}
45 @param stream: the midi stream to take a slice of
46 @type start: int
47 @param start: the start time in midi ticks
48 @type end: int or None
49 @param end: the end time in midi ticks, or None to go to the end
50
51 """
52 self.stream = stream
53 self.start = start
54 self.end = end
55
56 - def to_event_stream(self, repeat_playing=True, cancel_playing=False, all_off=False):
57 """
58 Performs the actual slice operation, producing a new event
59 stream for just the portion of the midi stream covered by the
60 slice.
61
62 @type repeat_playing: bool
63 @param repeat_playing: if True all notes currently being played
64 at the start point will be replayed at the beginning of the
65 result. Default: True
66 @type cancel_playing: bool
67 @param cancel_playing: if True, all notes being played at the end point
68 will be cancelled (by an appropriate note-off) and the end of
69 the result. Default: False
70 @type all_off: bool
71 @param all_off: if True, adds an All Notes Off event to the end of the
72 stream.
73
74 """
75
76
77 repeat_events = {}
78 last_repeat_event_time = 0
79 replay_notes = {}
80 cancel_notes = {}
81 for track_num,track in enumerate(self.stream):
82 track_repeat_events = {}
83 track_replay_notes = {}
84 track_cancel_notes = {}
85
86 ev_iter = iter(sorted(track))
87 try:
88 ev = ev_iter.next()
89
90 while ev.tick >= self.start:
91 event_type = type(ev)
92
93 if event_type in SLICE_REPEAT_EVENTS:
94 override_check = SLICE_REPEAT_EVENTS[event_type]
95
96 if event_type in track_repeat_events:
97 earlier = track_repeat_events[event_type]
98
99 earlier = [e for e in earlier if e.channel == ev.channel]
100
101
102 remove_events = []
103 for prior in earlier:
104 if override_check(ev, prior):
105 remove_events.append(id(prior))
106 track_repeat_events[event_type] = \
107 [e for e in track_repeat_events[event_type] \
108 if id(e) not in remove_events]
109
110 track_repeat_events.setdefault(event_type, []).append(ev)
111 elif repeat_playing or cancel_playing:
112 if isinstance(ev, NoteOnEvent) and ev.velocity > 0:
113
114
115 track_replay_notes[ev.pitch] = ev
116 elif (isinstance(ev, NoteOffEvent) or \
117 (isinstance(ev, NoteOnEvent) and ev.velocity == 0) \
118 ) and ev.pitch in track_replay_notes:
119
120
121 del track_replay_notes[ev.pitch]
122 ev = ev_iter.next()
123
124
125 if cancel_playing:
126
127 track_cancel_notes = track_replay_notes.copy()
128 while ev.tick < self.end:
129 if isinstance(ev, NoteOnEvent) and ev.velocity > 0:
130
131 track_cancel_notes[ev.pitch] = ev
132 elif (isinstance(ev, NoteOffEvent) or \
133 (isinstance(ev, NoteOnEvent) and ev.velocity == 0) \
134 ) and ev.pitch in track_cancel_notes:
135
136 del track_cancel_notes[ev.pitch]
137 ev = ev_iter.next()
138 except StopIteration:
139 pass
140
141
142 events_to_repeat = sum(track_repeat_events.values(), [])
143 if len(events_to_repeat):
144
145 events_to_repeat = [deepcopy(e) for e in events_to_repeat]
146 event_time_groups = {}
147 for ev in events_to_repeat:
148 event_time_groups.setdefault(ev.tick, []).append(ev)
149 repeat_events[track_num] = []
150 for new_time,time in enumerate(sorted(event_time_groups.keys())):
151
152 for e in event_time_groups[time]:
153 e.tick = new_time
154 repeat_events[track_num].extend(sum(event_time_groups.values(),[]))
155
156 last_repeat_event_time = max(last_repeat_event_time, new_time)
157
158 if repeat_playing:
159
160 replay_notes[track_num] = [deepcopy(ev) for ev in track_replay_notes.values()]
161
162 if cancel_playing:
163
164 cancel_notes[track_num] = [deepcopy(ev) for ev in track_cancel_notes.values()]
165
166
167 start_tempo = deepcopy(self.stream.get_tempo(self.start))
168 start_tempo.tick = last_repeat_event_time
169
170
171 new_str = EventStream()
172
173 new_str.format = self.stream.format
174 new_str.resolution = self.stream.resolution
175
176
177 for i,track in enumerate(self.stream):
178 new_str.add_track()
179
180
181 if i in repeat_events:
182 for ev in repeat_events[i]:
183
184 new_str.add_event(ev)
185
186
187 if i == 0:
188 new_str.add_event(start_tempo)
189
190 if repeat_playing:
191
192
193 for ev in replay_notes[i]:
194 ev.tick = last_repeat_event_time + 1
195 new_str.add_event(ev)
196
197
198 for ev in sorted(track):
199
200 if ev.tick < self.start:
201 continue
202 if self.end is not None and ev.tick >= self.end:
203 break
204
205 ev = deepcopy(ev)
206
207 ev.tick -= (self.start - last_repeat_event_time - 1)
208 new_str.add_event(ev)
209
210 if cancel_playing:
211
212 for ev in cancel_notes[i]:
213 noteoff = NoteOffEvent()
214 noteoff.tick = (self.end - self.start)
215 noteoff.pitch = ev.pitch
216 noteoff.channel = ev.channel
217 new_str.add_event(noteoff)
218
219 if all_off:
220
221 channels = list(set(ev.channel for ev in new_str.track))
222 for ch in channels:
223
224 all_off_ev = all_notes_off_event(ch)
225 all_off.tick = self.end - self.start + 1
226 new_str.add_event(all_off)
227
228
229 if self.end is not None:
230
231 eot = EndOfTrackEvent()
232 eot.tick = self.end
233 new_str.add_event(eot)
234
235 return new_str
236
237
238 __repeat_all = lambda x,y: False
239 __only_latest = lambda x,y: True
240
241 SLICE_REPEAT_EVENTS = {
242 AfterTouchEvent : lambda x,y: x.pitch == y.pitch,
243 ControlChangeEvent : lambda x,y: x.control == y.control,
244 ProgramChangeEvent : __only_latest,
245 ChannelAfterTouchEvent : __only_latest,
246 PitchWheelEvent : __only_latest,
247
248 SysExEvent : __repeat_all,
249
250 CopyrightEvent : __repeat_all,
251 TrackNameEvent : __repeat_all,
252 InstrumentNameEvent : __only_latest,
253 PortEvent : __only_latest,
254 TimeSignatureEvent : __only_latest,
255 KeySignatureEvent : __only_latest,
256 }
257 """
258 The event types that should be repeated at the beginning of a slice if
259 they occurred prior to the start of the slice in the event stream.
260
261 The repeat key function will be consulted to decide whether a later
262 event overrides an earlier one of the same type on the same channel.
263 The later event will be considered to have overridden the earlier if it
264 returns True.
265
266 ChannelPrefixEvent has a special behaviour and doesn't fit into this
267 framework.
268
269 """
270