Example #1
0
 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
Example #2
0
    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
Example #3
0
 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')
Example #4
0
 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
Example #5
0
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)
Example #6
0
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)
Example #7
0
    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
Example #8
0
 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
Example #9
0
    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
Example #10
0
 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
Example #11
0
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)
Example #12
0
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
Example #13
0
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)
Example #14
0
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