Package midi :: Module slice
[hide private]
[frames] | no frames]

Source Code for Module midi.slice

  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   
29 -class EventStreamSlice(object):
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 # Collect up events from prior to the start that we should 76 # repeat at the start of the new stream 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 # Stop once we've reached the start point 90 while ev.tick >= self.start: 91 event_type = type(ev) 92 # Only repeat events of certain types 93 if event_type in SLICE_REPEAT_EVENTS: 94 override_check = SLICE_REPEAT_EVENTS[event_type] 95 # Check whether this overrides earlier events 96 if event_type in track_repeat_events: 97 earlier = track_repeat_events[event_type] 98 # Only look at events on the same channel 99 earlier = [e for e in earlier if e.channel == ev.channel] 100 # If this overrides any of the earlier events, 101 # remove them 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 # Add this event 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 # Note sounded before the start: replay it unless 114 # it's subsequently taken off before the start 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 # Note's been played previously, but is taken off, 120 # so don't replay it 121 del track_replay_notes[ev.pitch] 122 ev = ev_iter.next() 123 124 # Continue to work out what notes are playing at the end if we need to 125 if cancel_playing: 126 # Start with the notes we know are playing at the beginning 127 track_cancel_notes = track_replay_notes.copy() 128 while ev.tick < self.end: 129 if isinstance(ev, NoteOnEvent) and ev.velocity > 0: 130 # New note starts 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 # Note ends naturally 136 del track_cancel_notes[ev.pitch] 137 ev = ev_iter.next() 138 except StopIteration: 139 pass 140 141 # Make each set of events at a distinct time take only one tick 142 events_to_repeat = sum(track_repeat_events.values(), []) 143 if len(events_to_repeat): 144 # Prepare the copy we'll use in the new version 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 # Give all simultaneous events the same time 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 # Keep track of the latest of these repeated events 156 last_repeat_event_time = max(last_repeat_event_time, new_time) 157 158 if repeat_playing: 159 # Prepare the notes we need to replay at the begining 160 replay_notes[track_num] = [deepcopy(ev) for ev in track_replay_notes.values()] 161 162 if cancel_playing: 163 # Prepare the notes we need to stop at the end 164 cancel_notes[track_num] = [deepcopy(ev) for ev in track_cancel_notes.values()] 165 166 # Work out the tempo at the start of the slice 167 start_tempo = deepcopy(self.stream.get_tempo(self.start)) 168 start_tempo.tick = last_repeat_event_time 169 170 # Now for the actual stream copying 171 new_str = EventStream() 172 # Copy the parameters from the old event stream 173 new_str.format = self.stream.format 174 new_str.resolution = self.stream.resolution 175 176 # Add the events from the old stream that are within the slice 177 for i,track in enumerate(self.stream): 178 new_str.add_track() 179 180 # Add in the events we need to repeat at the beginning 181 if i in repeat_events: 182 for ev in repeat_events[i]: 183 # We've already set the tick of these 184 new_str.add_event(ev) 185 186 # If this is the first track, add the start tempo 187 if i == 0: 188 new_str.add_event(start_tempo) 189 190 if repeat_playing: 191 # Add any notes that were played before the start and 192 # haven't been taken off yet 193 for ev in replay_notes[i]: 194 ev.tick = last_repeat_event_time + 1 195 new_str.add_event(ev) 196 197 # Add each event in the track 198 for ev in sorted(track): 199 # Only look at events in the range of the slice 200 if ev.tick < self.start: 201 continue 202 if self.end is not None and ev.tick >= self.end: 203 break 204 # Take a copy of the event from the source stream 205 ev = deepcopy(ev) 206 # Shift the event back 207 ev.tick -= (self.start - last_repeat_event_time - 1) 208 new_str.add_event(ev) 209 210 if cancel_playing: 211 # Add note-offs for any notes still playing at the end 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 # Find all channels that have been used 221 channels = list(set(ev.channel for ev in new_str.track)) 222 for ch in channels: 223 # Add an All Notes Off event for each channel 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 # If we have no end time, the EOT can just be set by the last event 229 if self.end is not None: 230 # Add an end-of-track event at the time the slice is supposed to stop 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 # Repeat all SysExes to be on the safe side - we don't know what they do 248 SysExEvent : __repeat_all, 249 # Always repeat these things, since they don't supercede earlier ones 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