craffel / pretty-midi

Utility functions for handling MIDI data in a nice/intuitive way.
MIT License
856 stars 151 forks source link

Trim Silence #212

Closed Mannerow closed 2 years ago

Mannerow commented 2 years ago

Hello,

I'm just wondering how you would suggest trimming silence on MIDI files? I have lots of data where I would like to remove silence from the start and end.

The only method I've found online involves converting to a piano_roll, however, there doesn't seem to be a function to convert back from a piano roll to PrettyMIDI.

I can see that the PrettyMIDI files have functions for getting the starting/ending time (estimate_beat_start, get_end_time). Is there any way to use these methods and change the MIDI file so that it only contains data in between the starting/end time?

Thanks

craffel commented 2 years ago

You can use get_end_time to get the time of the final event: https://github.com/craffel/pretty-midi/blob/main/pretty_midi/pretty_midi.py#L441 and make an almost-identical function (doing a min instead of a max) to get the start time. Then, you can do adjust_times([0, end_time - start_time], [start_time, end_time]).

Mannerow commented 2 years ago

Thanks, @craffel. I appreciate it!

Mannerow commented 2 years ago

Here's how I ended up trimming the starting silence (just the reverse of what you said):

        #Trim starting silence. 
        endTime = pm.get_end_time()
        startTime = get_start_time(pm)
        pm.adjust_times([startTime,endTime],[0,endTime-startTime])

Now I'm working on removing any silent bars, and I'm finding it a bit tricky. It seems like adjust_times isn't the best choice for this, since I actually want to cut these silences out completely. So, I'm trying to do it by iterating through each downbeat, converting this into an index into the piano_roll, and then deleting every section of the piano_roll that doesn't have any notes.

Here is my code:

        #Trim bars that are silent
        downbeats = pm.get_downbeats()[1:]
        pr = pm.get_piano_roll().T
        newPR = np.copy(pr)

        prev = 0
        for d in downbeats:
            current = (int)(round(d,2)*100)
            if (current < len(pr)):
                prSection = pr[prev:current]
                summed = np.sum(prSection, axis=0)
                all_zero = not np.any(summed)
                if all_zero:
                    # pdb.set_trace()
                    newPR = np.delete(newPR,slice(prev,current),0)
            prev = current
        pm = piano_roll_to_pretty_midi(newPR.T)

So far, this isn't working. Does anyone have any suggestions or maybe an easier way to do this?