From 42bd646644f208e3f422b34c1e92a38bf90dafc1 Mon Sep 17 00:00:00 2001 From: Tim Beyer <35711942+TimFelixBeyer@users.noreply.github.com> Date: Fri, 6 Nov 2020 02:32:28 +0100 Subject: [PATCH 1/2] Fix get_piano_roll rounding error with pedal A small issue that happens when a note offset happens just before/after the pedal is pressed down. Due to rounding, the pedal might be pressed after the note offset has already occurred. For example, both offset a and offset b round to t0, and would be held: t0-->t0+fs | * | pedal | * | offset a -> should not be held | * | offset b -> should be held --- pretty_midi/instrument.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/pretty_midi/instrument.py b/pretty_midi/instrument.py index 2960100..5299030 100644 --- a/pretty_midi/instrument.py +++ b/pretty_midi/instrument.py @@ -124,18 +124,31 @@ def get_piano_roll(self, fs=100, times=None, if pedal_threshold is not None: CC_SUSTAIN_PEDAL = 64 time_pedal_on = 0 + time_pedal_on_exact = 0 is_pedal_on = False + # Keep track of non-quantized offset times + end_times = np.zeros((128, int(fs * end_time))) + for note in self.notes: + end_times[note.pitch, int(note.start*fs):int(note.end*fs)] = note.end + for cc in [_e for _e in self.control_changes if _e.number == CC_SUSTAIN_PEDAL]: time_now = int(cc.time*fs) is_current_pedal_on = (cc.value >= pedal_threshold) if not is_pedal_on and is_current_pedal_on: time_pedal_on = time_now + time_pedal_on_exact = cc.time is_pedal_on = True elif is_pedal_on and not is_current_pedal_on: # For each pitch, a sustain pedal "retains" # the maximum velocity up to now due to # logarithmic nature of human loudness perception + + # Rounding might cause notes to be held for longer + # than would be correct, so we have to take the + # exact timing into account + held_notes = end_times[:, time_pedal_on] >= time_pedal_on_exact + piano_roll[:, time_pedal_on] *= held_notes subpr = piano_roll[:, time_pedal_on:time_now] # Take the running maximum From 8e965877edddd1907b58a190b4d6ab674701fbfb Mon Sep 17 00:00:00 2001 From: Tim Beyer <35711942+TimFelixBeyer@users.noreply.github.com> Date: Fri, 6 Nov 2020 02:46:41 +0100 Subject: [PATCH 2/2] Off-by-one --- pretty_midi/instrument.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pretty_midi/instrument.py b/pretty_midi/instrument.py index 5299030..7267bdc 100644 --- a/pretty_midi/instrument.py +++ b/pretty_midi/instrument.py @@ -118,7 +118,7 @@ def get_piano_roll(self, fs=100, times=None, for note in self.notes: # Should interpolate piano_roll[note.pitch, - int(note.start*fs):int(note.end*fs)] += note.velocity + int(note.start*fs):int(note.end*fs)+1] += note.velocity # Process sustain pedals if pedal_threshold is not None: @@ -129,7 +129,7 @@ def get_piano_roll(self, fs=100, times=None, # Keep track of non-quantized offset times end_times = np.zeros((128, int(fs * end_time))) for note in self.notes: - end_times[note.pitch, int(note.start*fs):int(note.end*fs)] = note.end + end_times[note.pitch, int(note.start*fs):int(note.end*fs)+1] = note.end for cc in [_e for _e in self.control_changes if _e.number == CC_SUSTAIN_PEDAL]: @@ -149,11 +149,11 @@ def get_piano_roll(self, fs=100, times=None, # exact timing into account held_notes = end_times[:, time_pedal_on] >= time_pedal_on_exact piano_roll[:, time_pedal_on] *= held_notes - subpr = piano_roll[:, time_pedal_on:time_now] + subpr = piano_roll[:, time_pedal_on:time_now+1] # Take the running maximum pedaled = np.maximum.accumulate(subpr, axis=1) - piano_roll[:, time_pedal_on:time_now] = pedaled + piano_roll[:, time_pedal_on:time_now+1] = pedaled is_pedal_on = False # Process pitch changes