def to_midi(self): """ Constructs a L{MIDI EventStream<midi.EventStream>} from the data in this stream. This can then be output to a file to be played. Note that TPCNotes will be output as normal MIDI notes. We can't do anything of the clever tuning stuff that we can do with tonal space coordinates, since we'd need to do a further step of analysis to work out the fully specified TS point from the pitch class. """ tempo = 120 from midi import EventStream, NoteOffEvent, NoteOnEvent, SetTempoEvent mid = EventStream() mid.add_track() # Set the tempo first at the beginning temp = SetTempoEvent() temp.tempo = tempo temp.tick = 0 mid.add_event(temp) # Work out how many ticks there are in a millisecond ticks_per_ms = float(mid.resolution) * tempo / 60000 # Create midi events for every event in our stream for ev in self.events: if isinstance(ev, TPCNoteEvent): # Create note-on and note-off events note = ev.note noteon = NoteOnEvent() noteon.pitch = note noteon.tick = int(ev.start * ticks_per_ms) noteon.velocity = 100 mid.add_event(noteon) noteoff = NoteOffEvent() noteoff.pitch = note noteoff.tick = int(ev.end * ticks_per_ms) noteoff.velocity = 100 mid.add_event(noteoff) elif isinstance(ev, (ChordEvent,BeatEvent)): # These events don't affect the midi data continue else: raise TypeError, "event type %s not recognised by "\ "MIDI converter." % type(ev).__name__ return mid
def to_midi(self): """ Constructs a L{MIDI EventStream<midi.EventStream>} from the data in this stream. This can then be output to a file to be played. Note that TPCNotes will be output as normal MIDI notes. We can't do anything of the clever tuning stuff that we can do with tonal space coordinates, since we'd need to do a further step of analysis to work out the fully specified TS point from the pitch class. """ tempo = 120 from midi import EventStream, NoteOffEvent, NoteOnEvent, SetTempoEvent mid = EventStream() mid.add_track() # Set the tempo first at the beginning temp = SetTempoEvent() temp.tempo = tempo temp.tick = 0 mid.add_event(temp) # Work out how many ticks there are in a millisecond ticks_per_ms = float(mid.resolution) * tempo / 60000 # Create midi events for every event in our stream for ev in self.events: if isinstance(ev, TPCNoteEvent): # Create note-on and note-off events note = ev.note noteon = NoteOnEvent() noteon.pitch = note noteon.tick = int(ev.start * ticks_per_ms) noteon.velocity = 100 mid.add_event(noteon) noteoff = NoteOffEvent() noteoff.pitch = note noteoff.tick = int(ev.end * ticks_per_ms) noteoff.velocity = 100 mid.add_event(noteoff) elif isinstance(ev, (ChordEvent, BeatEvent)): # These events don't affect the midi data continue else: raise TypeError, "event type %s not recognised by "\ "MIDI converter." % type(ev).__name__ return mid
def copy_event(event): if type(event) is SetTempoEvent: new_event = SetTempoEvent() new_event.tick = event.tick new_event.data[0] = event.data[0] new_event.data[1] = event.data[1] new_event.data[2] = event.data[2] return new_event elif type(event) is NoteOnEvent: new_event = NoteOnEvent() new_event.tick = event.tick new_event.channel = event.channel new_event.data[0] = event.data[0] new_event.data[1] = event.data[1] return new_event elif type(event) is NoteOffEvent: new_event = NoteOffEvent() new_event.tick = event.tick new_event.channel = event.channel new_event.data[0] = event.data[0] new_event.data[1] = event.data[1] return new_event elif type(event) is ProgramChangeEvent: new_event = ProgramChangeEvent new_event.tick = event.tick new_event.channel = event.channel new_event.data[0] = event.data[0] return new_event else: raise ('Not Supported')
def add_note_off_abs(track, pitch, end, channel, resolution=default_resolution): off = NoteOffEvent(tick=int(resolution * end), channel=channel, pitch=pitch) track.append(off) return track
def tonal_space_note_events(coord, start, duration, origin_note=60, velocity=100): """ Creates the MIDI events needed to play the given tonal space coordinate. These will be a single-note tuning event (to retune the appropriate note), a note-on event and a note-off event. @type coord: tuple @param coord: 3D tonal space coordinate @type start: int @param start: start time of note in ticks @type duration: int @param duration: length of the note in ticks @type origin_note: int @param origin_note: midi note number to assume the origin of the tonal space is equal to in pitch (default 60, middle C) @rtype: tuple @return: (tuning event, note on event, note off event) """ # Work out what note to retune for the root and how change = tonal_space_tuning(coord) note = change[0] # Create the tuning event tuning = single_note_tuning_event([change]) tuning.tick = start # Play the note note_on = NoteOnEvent() note_on.pitch = note note_on.velocity = velocity note_on.tick = start # Stop the note note_off = NoteOffEvent() note_off.pitch = note note_off.tick = start + duration note_off.velocity = velocity return (tuning, note_on, note_off)
def tonal_space_note_events(coord, start, duration, origin_note=60, velocity=100): """ Creates the MIDI events needed to play the given tonal space coordinate. These will be a single-note tuning event (to retune the appropriate note), a note-on event and a note-off event. @type coord: tuple @param coord: 3D tonal space coordinate @type start: int @param start: start time of note in ticks @type duration: int @param duration: length of the note in ticks @type origin_note: int @param origin_note: midi note number to assume the origin of the tonal space is equal to in pitch (default 60, middle C) @rtype: tuple @return: (tuning event, note on event, note off event) """ # Work out what note to retune for the root and how change = tonal_space_tuning(coord) note = change[0] # Create the tuning event tuning = single_note_tuning_event([change]) tuning.tick = start # Play the note note_on = NoteOnEvent() note_on.pitch = note note_on.velocity = velocity note_on.tick = start # Stop the note note_off = NoteOffEvent() note_off.pitch = note note_off.tick = start+duration note_off.velocity = velocity return (tuning, note_on, note_off)
def generate(self, overlay=None, offset=0): """ Generates a midi stream. """ octaves = 1 if overlay is not None: stream = overlay # Use organ sound instrument = 23 # Find the last channel used in the file we're overlaying channel = max(ev.channel for ev in stream.trackpool) + 1 volume = 50 else: stream = EventStream() stream.resolution = self.resolution # Just use piano instrument = 0 channel = 0 volume = 127 stream.add_track() pc = ProgramChangeEvent() pc.value = instrument pc.tick = 0 pc.channel = channel stream.add_event(pc) # Length of each chord in midi ticks chord_length = int(self.resolution * self.chord_length) times = [i * chord_length + offset for i in range(len(self.labels))] pending_note_offs = [] for label, time in zip(self.labels, times): chord_root = label.root # Work out the notes for this chord triad_notes = [(chord_root + note) % (octaves*12) + 72 for \ note in self.chord_vocab[label.label]] # Add the root in the octave two below triad_notes.append(chord_root + 48) # Add note offs for notes already on for noff in pending_note_offs: noff.tick = time - 1 stream.add_event(noff) pending_note_offs = [] if self.text_events: # Add a text event to represent the chord label tevent = LyricsEvent() tevent.data = "%s\n" % label tevent.tick = time stream.add_event(tevent) # Add a note-on and off event for each note for note in triad_notes: non = NoteOnEvent() non.tick = time non.pitch = note non.channel = channel non.velocity = volume stream.add_event(non) # Hold the note until the next chord is played noff = NoteOffEvent() noff.pitch = note noff.channel = channel noff.velocity = volume pending_note_offs.append(noff) # Add the last remaining note offs for noff in pending_note_offs: noff.tick = time + chord_length stream.add_event(noff) return stream
def generate(self, overlay=None, offset=0): """ Generates a midi stream. """ octaves = 1 if overlay is not None: stream = overlay # Use organ sound instrument = 23 # Find the last channel used in the file we're overlaying channel = max(ev.channel for ev in stream.trackpool) + 1 volume = 50 else: stream = EventStream() stream.resolution = self.resolution # Just use piano instrument = 0 channel = 0 volume = 127 stream.add_track() pc = ProgramChangeEvent() pc.value = instrument pc.tick = 0 pc.channel = channel stream.add_event(pc) # Length of each chord in midi ticks chord_length = int(self.resolution * self.chord_length) times = [i*chord_length + offset for i in range(len(self.labels))] pending_note_offs = [] for label,time in zip(self.labels, times): chord_root = label.root # Work out the notes for this chord triad_notes = [(chord_root + note) % (octaves*12) + 72 for \ note in self.chord_vocab[label.label]] # Add the root in the octave two below triad_notes.append(chord_root + 48) # Add note offs for notes already on for noff in pending_note_offs: noff.tick = time-1 stream.add_event(noff) pending_note_offs = [] if self.text_events: # Add a text event to represent the chord label tevent = LyricsEvent() tevent.data = "%s\n" % label tevent.tick = time stream.add_event(tevent) # Add a note-on and off event for each note for note in triad_notes: non = NoteOnEvent() non.tick = time non.pitch = note non.channel = channel non.velocity = volume stream.add_event(non) # Hold the note until the next chord is played noff = NoteOffEvent() noff.pitch = note noff.channel = channel noff.velocity = volume pending_note_offs.append(noff) # Add the last remaining note offs for noff in pending_note_offs: noff.tick = time+chord_length stream.add_event(noff) return stream
def generate(self, overlay=None, offset=0): """ Generates a midi stream. """ octaves = 1 if overlay is not None: stream = overlay # Use organ sound instrument = 23 # Find the last channel used in the file we're overlaying channel = max(ev.channel for ev in stream.trackpool) + 1 volume = 30 else: stream = EventStream() stream.resolution = self.resolution # Just use piano instrument = 0 channel = 0 volume = 127 stream.add_track() pc = ProgramChangeEvent() pc.value = instrument pc.tick = 0 pc.channel = channel stream.add_event(pc) # Length of each chord in midi ticks chord_length = int(self.resolution * self.chord_length) if self.times is None: times = [ i * chord_length + offset for i in range(len(self.labels)) ] else: times = [t + offset for t in self.times] formatter = getattr(self, 'formatter') pending_note_offs = [] for (tonic, mode, chord), time in zip(self.labels, times): scale_chord_root = constants.CHORD_NOTES[mode][chord][0] chord_root = (tonic + scale_chord_root) % 12 triad_type = constants.SCALE_TRIADS[mode][chord] # Work out the notes for this chord triad_notes = [(chord_root + note) % (octaves * 12) + 72 for note in constants.TRIAD_NOTES[triad_type]] # Add the root in the octave two below triad_notes.append(chord_root + 48) # Add note offs for notes already on for noff in pending_note_offs: noff.tick = time - 1 stream.add_event(noff) pending_note_offs = [] if self.text_events: # Add a text event to represent the chord label tevent = LyricsEvent() label = formatter((tonic, mode, chord)) tevent.data = "%s\n" % label tevent.tick = time stream.add_event(tevent) # Add a note-on and off event for each note for note in triad_notes: non = NoteOnEvent() non.tick = time non.pitch = note non.channel = channel non.velocity = volume stream.add_event(non) # Hold the note until the next chord is played noff = NoteOffEvent() noff.pitch = note noff.channel = channel noff.velocity = volume pending_note_offs.append(noff) # Add the last remaining note offs for noff in pending_note_offs: noff.tick = time + chord_length stream.add_event(noff) return stream
def generate(self, overlay=None, offset=0): """ Generates a midi stream. """ octaves = 1 if overlay is not None: stream = overlay # Use organ sound instrument = 23 # Find the last channel used in the file we're overlaying channel = max(ev.channel for ev in stream.trackpool) + 1 volume = 30 else: stream = EventStream() stream.resolution = self.resolution # Just use piano instrument = 0 channel = 0 volume = 127 stream.add_track() pc = ProgramChangeEvent() pc.value = instrument pc.tick = 0 pc.channel = channel stream.add_event(pc) # Length of each chord in midi ticks chord_length = int(self.resolution * self.chord_length) if self.times is None: times = [i*chord_length + offset for i in range(len(self.labels))] else: times = [t+offset for t in self.times] formatter = getattr(self, 'formatter') pending_note_offs = [] for (tonic,mode,chord),time in zip(self.labels, times): scale_chord_root = constants.CHORD_NOTES[mode][chord][0] chord_root = (tonic+scale_chord_root) % 12 triad_type = constants.SCALE_TRIADS[mode][chord] # Work out the notes for this chord triad_notes = [(chord_root + note) % (octaves*12) + 72 for note in constants.TRIAD_NOTES[triad_type]] # Add the root in the octave two below triad_notes.append(chord_root + 48) # Add note offs for notes already on for noff in pending_note_offs: noff.tick = time-1 stream.add_event(noff) pending_note_offs = [] if self.text_events: # Add a text event to represent the chord label tevent = LyricsEvent() label = formatter((tonic,mode,chord)) tevent.data = "%s\n" % label tevent.tick = time stream.add_event(tevent) # Add a note-on and off event for each note for note in triad_notes: non = NoteOnEvent() non.tick = time non.pitch = note non.channel = channel non.velocity = volume stream.add_event(non) # Hold the note until the next chord is played noff = NoteOffEvent() noff.pitch = note noff.channel = channel noff.velocity = volume pending_note_offs.append(noff) # Add the last remaining note offs for noff in pending_note_offs: noff.tick = time+chord_length stream.add_event(noff) return stream
def remove_duplicate_notes(stream, replay=False): """ Some processing operations, like L{simplify}, can leave a midi file with the same note being played twice at the same time. To avoid the confusion this leads to, it's best to remove these. This function will remove multiple instances of the same note being played simultaneously (in the same track and channel) and insert note-off events before a note is replayed that's already being played. This can lead to some strange effects if multiple instruments have been reduced to one, as in the case of L{simplify}. You may wish to keep seperate instruments on separate channels to avoid this. @type replay: bool @param replay: if True, notes that are played while they're already sounding while be replayed - taken off and put back on. Otherwise, such notes will be ignored. """ to_remove = [] to_add = {} for i, track in stream.tracklist.items(): notes_on = {} last_instance = {} for ev in sorted(track): if type(ev) == NoteOnEvent and ev.velocity > 0: # Note on if ev.channel in notes_on and \ ev.pitch in notes_on[ev.channel]: # Note is already being played previous = last_instance[ev.channel][ev.pitch] if not replay or previous.tick == ev.tick: # Simultaneous duplicate, or we don't want to replay # resounded notes # Remove this one to_remove.append(ev) else: # Replay: insert a note-off note_off = NoteOffEvent() note_off.pitch = ev.pitch note_off.channel = ev.channel note_off.velocity = 127 note_off.tick = ev.tick - 1 to_add.setdefault(i, []).append(note_off) # Increase the count of instances of this note being played notes_on.setdefault(ev.channel, {}).setdefault(ev.pitch, 0) notes_on[ev.channel][ev.pitch] += 1 last_instance.setdefault(ev.channel, {})[ev.pitch] = ev elif type(ev) == NoteOffEvent or \ (type(ev) == NoteOnEvent and ev.velocity == 0): # Note off if ev.channel not in notes_on or \ ev.pitch not in notes_on[ev.channel]: # Note is not currently being played # Remove this note off to_remove.append(ev) else: # Decrease the count of instances of this note being played notes_on[ev.channel][ev.pitch] -= 1 if notes_on[ev.channel][ev.pitch] == 0: # Note was only being played once # Leave the note off in there del notes_on[ev.channel][ev.pitch] else: # Note was being played multiple times # Decrease the count, but don't include this note off to_remove.append(ev) # Remove all events scheduled for removal stream.remove_event_instances(to_remove) # Add all events scheduled to addition for trk, evs in to_add.items(): stream.curtrack = trk for ev in evs: stream.add_event(ev)
def simplify(stream, remove_drums=False, remove_pc=False, remove_all_text=False, one_track=False, remove_tempo=False, remove_control=False, one_channel=False, remove_misc_control=False, real_note_offs=False, remove_duplicates=False): """ Filters a midi L{midi.EventStream} to simplify it. This is useful as a preprocessing step before taking midi input to an algorithm, for example, to make it clearer what the algorithm is using. Use kwargs to determine what filters will be applied. Without any kwargs, the stream will just be left as it was. Returns a filtered copy of the stream. @type remove_drums: bool @param remove_drums: filter out all channel 10 events @type remove_pc: bool @param remove_pc: filter out all program change events @type remove_all_text: bool @param remove_all_text: filter out any text events. This includes copyright, text, track name, lyrics. @type one_track: bool @param one_track: reduce everything to just one track @type remove_tempo: bool @param remove_tempo: filter out all tempo events @type remove_control: bool @param remove_control: filter out all control change events @type one_channel: bool @param one_channel: use only one channel: set the channel of every event to 0 @type remove_misc_control: bool @param remove_misc_control: filters a miscellany of device control events: aftertouch, channel aftertouch, pitch wheel, sysex, port @type real_note_offs: bool @param real_note_offs: replace 0-velocity note-ons with actual note-offs. Some midi files use one, some the other """ from midi import EventStream, TextEvent, ProgramChangeEvent, \ CopyrightEvent, TrackNameEvent, \ SetTempoEvent, ControlChangeEvent, AfterTouchEvent, \ ChannelAfterTouchEvent, PitchWheelEvent, SysExEvent, \ LyricsEvent, PortEvent, CuePointEvent, MarkerEvent, EndOfTrackEvent import copy # Empty stream to which we'll add the events we don't filter new_stream = EventStream() new_stream.resolution = stream.resolution new_stream.format = stream.format # Work out when the first note starts in the input stream input_start = first_note_tick(stream) # Filter track by track for track in stream: track_events = [] for ev in sorted(track): # Don't add EOTs - they get added automatically if type(ev) == EndOfTrackEvent: continue ev = copy.deepcopy(ev) # Each filter may modify the event or continue to filter it altogether if remove_drums: # Filter out any channel 10 events, which is typically # reserved for drums if ev.channel == 9 and \ type(ev) in (NoteOnEvent, NoteOffEvent): continue if remove_pc: # Filter out any program change events if type(ev) == ProgramChangeEvent: continue if remove_all_text: # Filter out any types of text event if type(ev) in (TextEvent, CopyrightEvent, TrackNameEvent, LyricsEvent, CuePointEvent, MarkerEvent): continue if remove_tempo: # Filter out any tempo events if type(ev) == SetTempoEvent: continue if remove_control: # Filter out any control change events if type(ev) == ControlChangeEvent: continue if remove_misc_control: # Filter out various types of control events if type(ev) in (AfterTouchEvent, ChannelAfterTouchEvent, ChannelAfterTouchEvent, PitchWheelEvent, SysExEvent, PortEvent): continue if real_note_offs: # Replace 0-velocity note-ons with note-offs if type(ev) == NoteOnEvent and ev.velocity == 0: new_ev = NoteOffEvent() new_ev.pitch = ev.pitch new_ev.channel = ev.channel new_ev.tick = ev.tick ev = new_ev if one_channel: ev.channel = 0 track_events.append(ev) # If there are events left in the track, add them all as a new track if len(track_events) > 1: if not one_track or len(new_stream.tracklist) == 0: new_stream.add_track() for ev in track_events: new_stream.add_event(ev) track_events = [] for track in stream: track.sort() # Work out when the first note happens now result_start = first_note_tick(new_stream) # Move all events after and including this sooner so the music # starts at the same point it did before shift = result_start - input_start before_start = max(input_start - 1, 0) if shift > 0: for ev in new_stream.trackpool: if ev.tick >= result_start: ev.tick -= shift elif ev.tick < result_start and ev.tick >= input_start: # This event happened in a region that no longer contains notes # Move it back to before what's now the first note ev.tick = before_start new_stream.trackpool.sort() if remove_duplicates: # Get rid of now duplicate events remove_duplicate_notes(new_stream, replay=True) return new_stream
def remove_duplicate_notes(stream, replay=False): """ Some processing operations, like L{simplify}, can leave a midi file with the same note being played twice at the same time. To avoid the confusion this leads to, it's best to remove these. This function will remove multiple instances of the same note being played simultaneously (in the same track and channel) and insert note-off events before a note is replayed that's already being played. This can lead to some strange effects if multiple instruments have been reduced to one, as in the case of L{simplify}. You may wish to keep seperate instruments on separate channels to avoid this. @type replay: bool @param replay: if True, notes that are played while they're already sounding while be replayed - taken off and put back on. Otherwise, such notes will be ignored. """ to_remove = [] to_add = {} for i,track in stream.tracklist.items(): notes_on = {} last_instance = {} for ev in sorted(track): if type(ev) == NoteOnEvent and ev.velocity > 0: # Note on if ev.channel in notes_on and \ ev.pitch in notes_on[ev.channel]: # Note is already being played previous = last_instance[ev.channel][ev.pitch] if not replay or previous.tick == ev.tick: # Simultaneous duplicate, or we don't want to replay # resounded notes # Remove this one to_remove.append(ev) else: # Replay: insert a note-off note_off = NoteOffEvent() note_off.pitch = ev.pitch note_off.channel = ev.channel note_off.velocity = 127 note_off.tick = ev.tick-1 to_add.setdefault(i, []).append(note_off) # Increase the count of instances of this note being played notes_on.setdefault(ev.channel, {}).setdefault(ev.pitch, 0) notes_on[ev.channel][ev.pitch] += 1 last_instance.setdefault(ev.channel, {})[ev.pitch] = ev elif type(ev) == NoteOffEvent or \ (type(ev) == NoteOnEvent and ev.velocity == 0): # Note off if ev.channel not in notes_on or \ ev.pitch not in notes_on[ev.channel]: # Note is not currently being played # Remove this note off to_remove.append(ev) else: # Decrease the count of instances of this note being played notes_on[ev.channel][ev.pitch] -= 1 if notes_on[ev.channel][ev.pitch] == 0: # Note was only being played once # Leave the note off in there del notes_on[ev.channel][ev.pitch] else: # Note was being played multiple times # Decrease the count, but don't include this note off to_remove.append(ev) # Remove all events scheduled for removal stream.remove_event_instances(to_remove) # Add all events scheduled to addition for trk,evs in to_add.items(): stream.curtrack = trk for ev in evs: stream.add_event(ev)
def simplify(stream, remove_drums=False, remove_pc=False, remove_all_text=False, one_track=False, remove_tempo=False, remove_control=False, one_channel=False, remove_misc_control=False, real_note_offs=False, remove_duplicates=False): """ Filters a midi L{midi.EventStream} to simplify it. This is useful as a preprocessing step before taking midi input to an algorithm, for example, to make it clearer what the algorithm is using. Use kwargs to determine what filters will be applied. Without any kwargs, the stream will just be left as it was. Returns a filtered copy of the stream. @type remove_drums: bool @param remove_drums: filter out all channel 10 events @type remove_pc: bool @param remove_pc: filter out all program change events @type remove_all_text: bool @param remove_all_text: filter out any text events. This includes copyright, text, track name, lyrics. @type one_track: bool @param one_track: reduce everything to just one track @type remove_tempo: bool @param remove_tempo: filter out all tempo events @type remove_control: bool @param remove_control: filter out all control change events @type one_channel: bool @param one_channel: use only one channel: set the channel of every event to 0 @type remove_misc_control: bool @param remove_misc_control: filters a miscellany of device control events: aftertouch, channel aftertouch, pitch wheel, sysex, port @type real_note_offs: bool @param real_note_offs: replace 0-velocity note-ons with actual note-offs. Some midi files use one, some the other """ from midi import EventStream, TextEvent, ProgramChangeEvent, \ CopyrightEvent, TrackNameEvent, \ SetTempoEvent, ControlChangeEvent, AfterTouchEvent, \ ChannelAfterTouchEvent, PitchWheelEvent, SysExEvent, \ LyricsEvent, PortEvent, CuePointEvent, MarkerEvent, EndOfTrackEvent import copy # Empty stream to which we'll add the events we don't filter new_stream = EventStream() new_stream.resolution = stream.resolution new_stream.format = stream.format # Work out when the first note starts in the input stream input_start = first_note_tick(stream) # Filter track by track for track in stream: track_events = [] for ev in sorted(track): # Don't add EOTs - they get added automatically if type(ev) == EndOfTrackEvent: continue ev = copy.deepcopy(ev) # Each filter may modify the event or continue to filter it altogether if remove_drums: # Filter out any channel 10 events, which is typically # reserved for drums if ev.channel == 9 and \ type(ev) in (NoteOnEvent, NoteOffEvent): continue if remove_pc: # Filter out any program change events if type(ev) == ProgramChangeEvent: continue if remove_all_text: # Filter out any types of text event if type(ev) in (TextEvent, CopyrightEvent, TrackNameEvent, LyricsEvent, CuePointEvent, MarkerEvent): continue if remove_tempo: # Filter out any tempo events if type(ev) == SetTempoEvent: continue if remove_control: # Filter out any control change events if type(ev) == ControlChangeEvent: continue if remove_misc_control: # Filter out various types of control events if type(ev) in (AfterTouchEvent, ChannelAfterTouchEvent, ChannelAfterTouchEvent, PitchWheelEvent, SysExEvent, PortEvent): continue if real_note_offs: # Replace 0-velocity note-ons with note-offs if type(ev) == NoteOnEvent and ev.velocity == 0: new_ev = NoteOffEvent() new_ev.pitch = ev.pitch new_ev.channel = ev.channel new_ev.tick = ev.tick ev = new_ev if one_channel: ev.channel = 0 track_events.append(ev) # If there are events left in the track, add them all as a new track if len(track_events) > 1: if not one_track or len(new_stream.tracklist) == 0: new_stream.add_track() for ev in track_events: new_stream.add_event(ev) track_events = [] for track in stream: track.sort() # Work out when the first note happens now result_start = first_note_tick(new_stream) # Move all events after and including this sooner so the music # starts at the same point it did before shift = result_start - input_start before_start = max(input_start-1, 0) if shift > 0: for ev in new_stream.trackpool: if ev.tick >= result_start: ev.tick -= shift elif ev.tick < result_start and ev.tick >= input_start: # This event happened in a region that no longer contains notes # Move it back to before what's now the first note ev.tick = before_start new_stream.trackpool.sort() if remove_duplicates: # Get rid of now duplicate events remove_duplicate_notes(new_stream, replay=True) return new_stream