Skip to content

Commit

Permalink
- Add pedal for chroma and piano roll (craffel#130)
Browse files Browse the repository at this point in the history
  • Loading branch information
maezawa-akira committed Apr 10, 2018
1 parent d7ef6ee commit e5e3747
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 24 deletions.
47 changes: 34 additions & 13 deletions pretty_midi/instrument.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,9 @@ def get_onsets(self):
# Return them sorted (because why not?)
return np.sort(onsets)

def get_piano_roll(self, fs=100, times=None, sustain_pedal_elongates_note=False, sustain_pedal_elongate_thres=64):
def get_piano_roll(self, fs=100, times=None,
use_pedal=False,
pedal_threshold=64):
"""Compute a piano roll matrix of this instrument.
Parameters
Expand All @@ -84,12 +86,15 @@ def get_piano_roll(self, fs=100, times=None, sustain_pedal_elongates_note=False,
times : np.ndarray
Times of the start of each column in the piano roll.
Default ``None`` which is ``np.arange(0, get_end_time(), 1./fs)``.
sustain_pedal_elongates_note : Boolean
Control Change 64 (Sustain pedal) is reflected as elongation of notes.
Default is False, which does nothing. If True, then CC64 value greater
than sustain_pedal_elongate_thres will be treated as pedal on and rest as pedal off.
sustain_pedal_elongate_thres : Int
The threshold value for treating CC64 message as elongation of note.
use_pedal : Boolean
Control Change 64 (Sustain pedal) is reflected as elongation of
notes.
Default is False, which does nothing. If True, then CC64 value
greater than pedal_threshold will be treated as pedal on and
the rest as pedal off.
pedal_threshold : Int
The threshold value for treating CC64 message as elongation of a
note.
Default is ``64``
Returns
Expand Down Expand Up @@ -121,19 +126,22 @@ def get_piano_roll(self, fs=100, times=None, sustain_pedal_elongates_note=False,
int(note.start*fs):int(note.end*fs)] += note.velocity

# Process sustain pedals
if sustain_pedal_elongates_note:
if use_pedal:
CC_SUSTAIN_PEDAL = 64
time_pedal_on = 0
is_pedal_on = False
for cc in [_e for _e in self.control_changes if _e.number == CC_SUSTAIN_PEDAL]:
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 > sustain_pedal_elongate_thres)
is_current_pedal_on = (cc.value > pedal_threshold)
if not is_pedal_on and is_current_pedal_on:
time_pedal_on = time_now
is_pedal_on = True
elif is_pedal_on and not is_current_pedal_on:
subpr = piano_roll[:, time_pedal_on:time_now]
piano_roll[:, time_pedal_on:time_now] = np.minimum( subpr.cumsum(1), subpr.max(1)[:,np.newaxis])
pedaled = np.minimum(subpr.cumsum(1),
subpr.max(1)[:, np.newaxis])
piano_roll[:, time_pedal_on:time_now] = pedaled
is_pedal_on = False

# Process pitch changes
Expand Down Expand Up @@ -186,7 +194,8 @@ def get_piano_roll(self, fs=100, times=None, sustain_pedal_elongates_note=False,
axis=1)
return piano_roll_integrated

def get_chroma(self, fs=100, times=None):
def get_chroma(self, fs=100, times=None, use_pedal=False,
pedal_threshold=64):
"""Get a sequence of chroma vectors from this instrument.
Parameters
Expand All @@ -197,6 +206,16 @@ def get_chroma(self, fs=100, times=None):
times : np.ndarray
Times of the start of each column in the piano roll.
Default ``None`` which is ``np.arange(0, get_end_time(), 1./fs)``.
use_pedal : Boolean
Control Change 64 (Sustain pedal) is reflected as elongation of
notes.
Default is False, which does nothing. If True, then CC64 value
greater than pedal_threshold will be treated as pedal on and
the rest as pedal off.
pedal_threshold : Int
The threshold value for treating CC64 message as elongation of a
note.
Default is ``64``
Returns
-------
Expand All @@ -205,7 +224,9 @@ def get_chroma(self, fs=100, times=None):
"""
# First, get the piano roll
piano_roll = self.get_piano_roll(fs=fs, times=times)
piano_roll = self.get_piano_roll(fs=fs, times=times,
use_pedal=use_pedal,
pedal_threshold=pedal_threshold)
# Fold into one octave
chroma_matrix = np.zeros((12, piano_roll.shape[1]))
for note in range(12):
Expand Down
40 changes: 29 additions & 11 deletions pretty_midi/pretty_midi.py
Original file line number Diff line number Diff line change
Expand Up @@ -739,7 +739,9 @@ def get_onsets(self):
# Return them sorted (because why not?)
return np.sort(onsets)

def get_piano_roll(self, fs=100, times=None, sustain_pedal_elongates_note=False, sustain_pedal_elongate_thres=64):
def get_piano_roll(self, fs=100, times=None,
use_pedal=False,
pedal_threshold=64):
"""Compute a piano roll matrix of the MIDI data.
Parameters
Expand All @@ -750,12 +752,15 @@ def get_piano_roll(self, fs=100, times=None, sustain_pedal_elongates_note=False,
times : np.ndarray
Times of the start of each column in the piano roll.
Default ``None`` which is ``np.arange(0, get_end_time(), 1./fs)``.
sustain_pedal_elongates_note : Boolean
Control Change 64 (Sustain pedal) is reflected as elongation of notes.
Default is False, which does nothing. If True, then CC64 value greater
than sustain_pedal_elongate_thres will be treated as pedal on and rest as pedal off.
sustain_pedal_elongate_thres : Int
The threshold value for treating CC64 message as elongation of note.
use_pedal : Boolean
Control Change 64 (Sustain pedal) is reflected as elongation of
notes.
Default is False, which does nothing. If True, then CC64 value
greater than pedal_threshold will be treated as pedal on and
the rest as pedal off.
pedal_threshold : Int
The threshold value for treating CC64 message as elongation of a
note.
Default is ``64``
Returns
Expand All @@ -771,8 +776,8 @@ def get_piano_roll(self, fs=100, times=None, sustain_pedal_elongates_note=False,

# Get piano rolls for each instrument
piano_rolls = [i.get_piano_roll(fs=fs, times=times,
sustain_pedal_elongates_note=sustain_pedal_elongates_note,
sustain_pedal_elongate_thres=sustain_pedal_elongate_thres)
use_pedal=use_pedal,
pedal_threshold=pedal_threshold)
for i in self.instruments]
# Allocate piano roll,
# number of columns is max of # of columns in all piano rolls
Expand Down Expand Up @@ -842,7 +847,8 @@ def get_pitch_class_transition_matrix(self, normalize=False,

return pc_trans_mat

def get_chroma(self, fs=100, times=None):
def get_chroma(self, fs=100, times=None, use_pedal=False,
pedal_threshold=64):
"""Get the MIDI data as a sequence of chroma vectors.
Parameters
Expand All @@ -853,6 +859,16 @@ def get_chroma(self, fs=100, times=None):
times : np.ndarray
Times of the start of each column in the piano roll.
Default ``None`` which is ``np.arange(0, get_end_time(), 1./fs)``.
use_pedal : Boolean
Control Change 64 (Sustain pedal) is reflected as elongation of
notes.
Default is False, which does nothing. If True, then CC64 value
greater than pedal_threshold will be treated as pedal on and
the rest as pedal off.
pedal_threshold : Int
The threshold value for treating CC64 message as elongation of a
note.
Default is ``64``
Returns
-------
Expand All @@ -861,7 +877,9 @@ def get_chroma(self, fs=100, times=None):
"""
# First, get the piano roll
piano_roll = self.get_piano_roll(fs=fs, times=times)
piano_roll = self.get_piano_roll(fs=fs, times=times,
use_pedal=use_pedal,
pedal_threshold=pedal_threshold)
# Fold into one octave
chroma_matrix = np.zeros((12, piano_roll.shape[1]))
for note in range(12):
Expand Down

0 comments on commit e5e3747

Please sign in to comment.