Exemplo n.º 1
0
def get_beat_time(
        pm: PrettyMIDI,
        beat_division=4
) -> Tuple[ndarray, ndarray, ndarray, List[int], List[int]]:
    beats = pm.get_beats()

    beats = np.unique(beats, axis=0)

    divided_beats = []
    for i in range(len(beats) - 1):
        for j in range(beat_division):
            divided_beats.append((beats[i + 1] - beats[i]) / beat_division *
                                 j + beats[i])
    divided_beats.append(beats[-1])
    divided_beats = np.unique(divided_beats, axis=0)

    beat_indices = []
    for beat in beats:
        beat_indices.append(np.argwhere(divided_beats == beat)[0][0])

    down_beats = pm.get_downbeats()
    if divided_beats[-1] > down_beats[-1]:
        down_beats = np.append(
            down_beats, down_beats[-1] - down_beats[-2] + down_beats[-1])

    down_beats = np.unique(down_beats, axis=0)

    down_beat_indices = []
    for down_beat in down_beats:

        down_beat_indices.append(np.argmin(np.abs(down_beat - divided_beats)))

    return np.array(divided_beats), np.array(beats), np.array(
        down_beats), beat_indices, down_beat_indices
Exemplo n.º 2
0
 def __init__(self, path):
     pm = PrettyMIDI(path)
     pm.remove_invalid_notes()
     self.midi = pm
     self.tempo = pm.estimate_tempo()
     self.beat_times = pm.get_beats()
     self.bar_times = pm.get_downbeats()
     self.end_time = pm.get_end_time()
     self.instruments = pm.instruments
def get_bass_drums_on_beat(pm_drums: PrettyMIDI) -> float:
    """
  Returns the ratio of the bass drums that fall directly on a beat.

  :param pm_drums: the PrettyMIDI instance to analyse
  :return: the ratio of the bass drums that fall on a beat
  """
    beats = pm_drums.get_beats()
    bass_drums = [
        note.start for note in pm_drums.instruments[0].notes
        if note.pitch == 35 or note.pitch == 36
    ]
    bass_drums_on_beat = []
    for beat in beats:
        beat_has_bass_drum = False
        for bass_drum in bass_drums:
            if math.isclose(beat, bass_drum):
                beat_has_bass_drum = True
                break
        bass_drums_on_beat.append(True if beat_has_bass_drum else False)
    num_bass_drums_on_beat = len([bd for bd in bass_drums_on_beat if bd])
    return num_bass_drums_on_beat / len(bass_drums_on_beat)
Exemplo n.º 4
0
    def plot(self, pm: PrettyMIDI):
        """
        Plots the pretty midi object as a plot object.

          :param pm: the PrettyMIDI instance to plot
          :return: the bokeh plot layout
        """
        preset = self._preset

        # Calculates the QPM from the MIDI file, might raise exception if confused
        qpm = self._get_qpm(pm)

        # Initialize the tools, those are present on the right hand side
        plot = bokeh.plotting.figure(tools="reset,hover,save,wheel_zoom,pan",
                                     toolbar_location=preset.toolbar_location)

        # Setup the hover and the data dict for bokeh,
        # each property must match a property in the data dict
        plot.select(dict(type=bokeh.models.HoverTool)).tooltips = ({
            "program":
            "@program",
            "pitch":
            "@top",
            "velocity":
            "@velocity",
            "duration":
            "@duration",
            "start_time":
            "@left",
            "end_time":
            "@right"
        })
        data = dict(program=[],
                    top=[],
                    bottom=[],
                    left=[],
                    right=[],
                    duration=[],
                    velocity=[],
                    color=[])

        # Puts the notes in the dict for bokeh and saves first
        # and last note time, bigger and smaller pitch
        pitch_min = None
        pitch_max = None
        first_note_start = None
        last_note_end = None
        index_instrument = 0
        for instrument in pm.instruments:
            for note in instrument.notes:
                pitch_min = min(pitch_min or self._MAX_PITCH, note.pitch)
                pitch_max = max(pitch_max or self._MIN_PITCH, note.pitch)
                color = self._get_color(index_instrument, note)
                note_start = note.start
                note_end = note.start + (note.end - note.start)
                data["program"].append(instrument.program)
                data["top"].append(note.pitch)
                if self._show_velocity:
                    data["bottom"].append(note.pitch + (note.velocity / 127))
                else:
                    data["bottom"].append(note.pitch + 1)
                data["left"].append(note_start)
                data["right"].append(note_end)
                data["duration"].append(note_end - note_start)
                data["velocity"].append(note.velocity)
                data["color"].append(color)
                first_note_start = min(first_note_start or sys.maxsize,
                                       note_start)
                last_note_end = max(last_note_end or 0, note_end)
            index_instrument = index_instrument + 1

        # Shows an empty plot even if there are no notes
        if first_note_start is None or last_note_end is None or pitch_min is None or pitch_max is None:
            pitch_min = self._MIN_PITCH
            pitch_max = pitch_min + 5
            first_note_start = 0
            last_note_end = 0

        # Gets the pitch range (min, max) from either the provided arguments
        # or min and max values from the notes
        if self._plot_pitch_range_start is not None:
            pitch_min = self._plot_pitch_range_start
        else:
            pitch_min = min(self._MAX_PITCH, pitch_min)
        if self._plot_pitch_range_stop is not None:
            pitch_max = self._plot_pitch_range_stop
        else:
            pitch_max = max(self._MIN_PITCH, pitch_max)

        pitch_range = pitch_max + 1 - pitch_min

        # Draws the rectangles on the plot from the data
        source = ColumnDataSource(data=data)
        plot.quad(left="left",
                  right="right",
                  top="top",
                  bottom="bottom",
                  line_alpha=1,
                  line_color="black",
                  color="color",
                  source=source)

        # Draws the y grid by hand, because the grid has label on the ticks, but
        # for a plot like this, the labels needs to fit in between the ticks.
        # Also useful to change the background of the grid each line
        for pitch in range(pitch_min, pitch_max + 1):
            # Draws the background box and contours, on the underlay layer, so
            # that the rectangles and over the box annotations
            fill_alpha = (0.15 if pitch % 2 == 0 else 0.00)
            box = BoxAnnotation(bottom=pitch,
                                top=pitch + 1,
                                fill_color="gray",
                                fill_alpha=fill_alpha,
                                line_color="black",
                                line_alpha=0.3,
                                line_width=1,
                                level="underlay")
            plot.add_layout(box)
            label = Label(x=preset.label_y_axis_offset_x,
                          y=pitch + preset.label_y_axis_offset_y,
                          x_units="screen",
                          text=str(pitch),
                          render_mode="css",
                          text_font_size=preset.label_text_font_size,
                          text_font_style=preset.label_text_font_style)
            plot.add_layout(label)

        # Gets the time signature from pretty midi, or 4/4 if none
        if self._midi_time_signature:
            numerator, denominator = self._midi_time_signature.split("/")
            time_signature = TimeSignature(int(numerator), int(denominator), 0)
        else:
            if pm.time_signature_changes:
                if len(pm.time_signature_changes) > 1:
                    raise Exception(
                        "Multiple time signatures are not supported")
                time_signature = pm.time_signature_changes[0]
            else:
                time_signature = TimeSignature(4, 4, 0)

        # Gets seconds per bar and seconds per beat
        if len(pm.get_beats()) >= 2:
            seconds_per_beat = pm.get_beats()[1] - pm.get_beats()[0]
        else:
            seconds_per_beat = 0.5
        if len(pm.get_downbeats()) >= 2:
            seconds_per_bar = pm.get_downbeats()[1] - pm.get_downbeats()[0]
        else:
            seconds_per_bar = 2.0

        # Defines the end time of the plot in seconds
        if self._plot_bar_range_stop is not None:
            plot_end_time = self._plot_bar_range_stop * seconds_per_bar
        else:
            # Calculates the plot start and end time, the start time can start after
            # notes or truncate notes if the plot is too long (we left truncate the
            # plot with the bounds)
            # The plot start and plot end are a multiple of seconds per bar (closest
            # smaller value for the start time, closest higher value for the end time)
            plot_end_time = int(
                (last_note_end) / seconds_per_bar) * seconds_per_bar
            # If the last note end is exactly on a multiple of seconds per bar,
            # we don't start a new one
            is_on_bar = math.isclose(last_note_end % seconds_per_bar,
                                     seconds_per_bar)
            is_on_bar_exact = math.isclose(last_note_end % seconds_per_bar,
                                           0.0)
            if not is_on_bar and not is_on_bar_exact:
                plot_end_time += seconds_per_bar

        # Defines the start time of the plot in seconds
        if self._plot_bar_range_start is not None:
            plot_start_time = self._plot_bar_range_start * seconds_per_bar
        else:
            start_time = int(
                first_note_start / seconds_per_bar) * seconds_per_bar
            plot_max_length_time = self._plot_max_length_bar * seconds_per_bar
            plot_start_time = max(plot_end_time - plot_max_length_time,
                                  start_time)

        # Draws the vertical bar grid, with a different background color
        # for each bar
        if preset.show_bar:
            bar_count = 0
            for bar_time in pm.get_downbeats():
                fill_alpha_index = bar_count % len(self._bar_fill_alphas)
                fill_alpha = self._bar_fill_alphas[fill_alpha_index]
                box = BoxAnnotation(left=bar_time,
                                    right=bar_time + seconds_per_bar,
                                    fill_color="gray",
                                    fill_alpha=fill_alpha,
                                    line_color="black",
                                    line_width=2,
                                    line_alpha=0.5,
                                    level="underlay")
                plot.add_layout(box)
                bar_count += 1

        # Draws the vertical beat grid, those are only grid lines
        if preset.show_beat:
            for beat_time in pm.get_beats():
                box = BoxAnnotation(left=beat_time,
                                    right=beat_time + seconds_per_beat,
                                    fill_color=None,
                                    line_color="black",
                                    line_width=1,
                                    line_alpha=0.4,
                                    level="underlay")
                plot.add_layout(box)

        # Configure x axis
        plot.xaxis.bounds = (plot_start_time, plot_end_time)
        plot.xaxis.axis_label = "time (SEC)"
        plot.xaxis.axis_label_text_font_size = preset.axis_label_text_font_size
        plot.xaxis.ticker = bokeh.models.SingleIntervalTicker(interval=1)
        plot.xaxis.major_tick_line_alpha = 0.9
        plot.xaxis.major_tick_line_width = 1
        plot.xaxis.major_tick_out = preset.axis_x_major_tick_out
        plot.xaxis.minor_tick_line_alpha = 0
        plot.xaxis.major_label_text_font_size = preset.label_text_font_size
        plot.xaxis.major_label_text_font_style = preset.label_text_font_style

        # Configure y axis
        plot.yaxis.bounds = (pitch_min, pitch_max + 1)
        plot.yaxis.axis_label = "pitch (MIDI)"
        plot.yaxis.axis_label_text_font_size = preset.axis_label_text_font_size
        plot.yaxis.ticker = bokeh.models.SingleIntervalTicker(interval=1)
        plot.yaxis.major_label_text_alpha = 0
        plot.yaxis.major_tick_line_alpha = 0.9
        plot.yaxis.major_tick_line_width = 1
        plot.yaxis.major_tick_out = preset.axis_y_major_tick_out
        plot.yaxis.minor_tick_line_alpha = 0
        plot.yaxis.axis_label_standoff = preset.axis_y_label_standoff
        plot.outline_line_width = 1
        plot.outline_line_alpha = 1
        plot.outline_line_color = "black"

        # The x grid is deactivated because is draw by hand (see x grid code)
        plot.xgrid.grid_line_color = None

        # The y grid is deactivated because is draw by hand (see y grid code)
        plot.ygrid.grid_line_color = None

        # Configure the plot size and range
        plot_title_text = "Visual MIDI (%s QPM, %s/%s)" % (str(
            int(qpm)), time_signature.numerator, time_signature.denominator)
        plot.title = Title(text=plot_title_text,
                           text_font_size=preset.title_text_font_size)
        plot.plot_width = preset.plot_width
        if preset.row_height:
            plot.plot_height = pitch_range * preset.row_height
        else:
            plot.plot_height = preset.plot_height
        plot.x_range = Range1d(plot_start_time, plot_end_time)
        plot.y_range = Range1d(pitch_min, pitch_max + 1)
        plot.min_border_right = 50

        if self._live_reload and preset.stop_live_reload_button:
            callback = CustomJS(code="clearInterval(liveReloadInterval)")
            button = Button(label="stop live reload")
            button.js_on_click(callback)
            layout = column(button, plot)
        else:
            layout = column(plot)

        return layout
Exemplo n.º 5
0
def identify_key(midi_filename, command_line_print = True, save_results = True, measure_value = 1):
  """ Runs key identification algorithm

  :parameters:
    - midi_filename : String of name of existing midi file to process.
    - command_line_print : Boolean to allow for printing results to command line.
    - save_results : Boolean to allow saving the approximated key and the names of those keys to a .npz file matching the midi files name
    - measure_value : int > 0 that sets how many beats the program will process per approximation.
    """

  song = PrettyMIDI(midi.read_midifile(midi_filename))

  #get beat locations for slices
  beats = song.get_beats() #output is an np.ndarray
  times = beats.flatten()
  sectionBeats = True
  #create slices of chroma data to process including summing them up
  #output: every "measure" of beats

  if measure_value< 1 or measure_value >times.size or measure_value == None:
    sectionBeats = False
    print "WARNING: measure_value selected less than 1 or greater than beat size.  Will do one approximation for the whole song."



  key_weight, names = load_keys()
  #get Chroma features
  chroma = song.get_chroma()
  #normalize chroma features
  chroma /= (chroma.max( axis = 0 ) + (chroma.max( axis = 0 ) == 0))

  # first case
  if sectionBeats:
    chroma_slice = chroma[:,0:round(times[0])*100]
  else:
    chroma_slice = chroma
  # Sum rows to find intensity of each note.
  vec = np.sum(chroma_slice, axis=1)
  keys_approx = get_approx_key(vec, key_weight)

  #for each slice, get approximated key and score into 2 column array (key, score)
  #possiblymay need to use indices of key names instead of actual keys
  #chroma time indices have a resolution of 10 ms

  times_used = np.array([[times[0]]])

  if sectionBeats:
    for t in range(1, times.size-1,measure_value):
    #make chroma slice based on time
      if times.size -t < measure_value:
        chroma_slice = chroma[:,round(times[t]*100):round(times[t+1]*100)]
      # print chroma_slice
      # vec = make_chroma_vector(chroma_slice)
        vec = np.sum(chroma_slice, axis=1)
      # vec = vec[::-1]
      else:
        chroma_slice = chroma[:,round(times[t]*100):round(times[t+1]*100)]
      # print chroma_slice
      # vec = make_chroma_vector(chroma_slice)
        vec = np.sum(chroma_slice, axis=1)
      # vec = vec[::-1]
      apr = get_approx_key(vec, key_weight)
      #if the score isn't 0 (which happens in silence), add the approximated key to the list
      if not apr[0,1] == 0:
        keys_approx = np.vstack((keys_approx, apr))
        times_used = np.vstack((times_used, np.array([[times[t]]])))


  # DUMMIED OUT CODE FOR FUTURE IMPLEMENTATION
  # final_keys = np.array([ [ keys_approx[0,0],times[0,0] ] ]) #mark first
  # print final_keys
  #
  # #iterate through rows of array- if there is a change, get % difference in scores for each key and use threshold to figure
  # #if it is a key change.  mark key change in final 2 column array of (key, time start)
  # threshold = .15 #experimental value
  #
  # if times.size > 1:
  #   print "going thru removal loop"
  #   for t in range (1, keys_approx.shape[0]):
  #     current = keys_approx[t,0]
  #     prev = keys_approx[t-1,0]
  #     if not current == prev: #if not equal to previous, check % difference of scores
  #       print "In key change check"
  #       score1 = keys_approx[t,1] #score of key of this time slice
  #       # print score1
  #       vec = make_chroma_vector(chroma, round(times[0,t])*100,round(times[0,t+1])*100 )
  #       # print vec
  #       score2 = get_key_score(vec, key_weight, prev) #score it would have gotten with last input key
  #       # print score2
  #       diff = abs(score1-score2)/(score1+score2)
  #       print diff
  #       if diff > threshold:
  #         arr = np.array([[keys_approx[t,0], times[t,0] ]])
  #         # print arr
  #         print "Key change at index: ", times[t,0]
  #         final_keys = np.vstack((final_keys, arr))
  #       else:
  #         print "difference not large enough to constitute key change "

  keys_approx = final_keys
  e = keys_approx.shape[0]
  if command_line_print:
    for i in range(0, e):
      key_int = int(keys_approx[i,0])
      print "In key: ",names[0,key_int],"at time: ", times_used[i,0]
  if save_results:
    filename = os.path.basename(midi_filename)
    filename_raw = os.path.splitext(filename)[0]
    # if not os.path.exists(directory):
    dirname = "Results_of_key_approximation"
    if not os.path.exists(dirname):
      os.makedirs(dirname)
    np.savez(dirname+"/"+filename_raw+"_key_approx-vars", keys_approx = keys_approx, names = names)
    file_results = open(dirname+"/"+filename_raw+"_key_approx-text_form.txt",'w')
    file_results.write(filename_raw+"\n")
    for i in range(0, e):
      key_int = int(keys_approx[i,0])
      file_results.write("In key: "+names[0,key_int]+" at time: "+ str(times_used[i,0])+"\n")
    file_results.close()
  return keys_approx, names
Exemplo n.º 6
0
def from_pretty_midi(
    midi: PrettyMIDI,
    resolution: int = DEFAULT_RESOLUTION,
    mode: str = "max",
    algorithm: str = "normal",
    collect_onsets_only: bool = False,
    first_beat_time: Optional[float] = None,
) -> Multitrack:
    """Return a Multitrack object converted from a PrettyMIDI object.

    Parse a :class:`pretty_midi.PrettyMIDI` object. The data type of the
    resulting piano rolls is automatically determined (int if 'mode' is
    'sum' and np.uint8 if `mode` is 'max').

    Parameters
    ----------
    midi : :class:`pretty_midi.PrettyMIDI`
        PrettyMIDI object to parse.
    mode : {'max', 'sum'}
        Merging strategy for duplicate notes. Defaults to 'max'.
    algorithm : {'normal', 'strict', 'custom'}
        Algorithm for finding the location of the first beat (see
        Notes). Defaults to 'normal'.
    collect_onsets_only : bool
        True to collect only the onset of the notes (i.e. note on
        events) in all tracks, where the note off and duration
        information are discarded. False to parse regular piano rolls.
        Defaults to False.
    first_beat_time : float, optional
        Location of the first beat, in sec. Required and only
        effective when using 'custom' algorithm.

    Returns
    -------
    :class:`pypianoroll.Multitrack`
        Converted Multitrack object.

    Notes
    -----
    There are three algorithms for finding the location of the first
    beat:

    - 'normal' : Estimate the location of the first beat using
      :meth:`pretty_midi.PrettyMIDI.estimate_beat_start`.
    - 'strict' : Set the location of the first beat to the time of the
      first time signature change. Raise a RuntimeError if no time
      signature change is found.
    - 'custom' : Set the location of the first beat to the value of
      argument `first_beat_time`. Raise a ValueError if
      `first_beat_time` is not given.

    If an incomplete beat before the first beat is found, an additional
    beat will be added before the (estimated) beat starting time.
    However, notes before the (estimated) beat starting time for more
    than one beat are dropped.

    """
    if mode not in ("max", "sum"):
        raise ValueError("`mode` must be either 'max' or 'sum'.")

    # Set first_beat_time for 'normal' and 'strict' modes
    if algorithm == "normal":
        if midi.time_signature_changes:
            midi.time_signature_changes.sort(key=lambda x: x.time)
            first_beat_time = midi.time_signature_changes[0].time
        else:
            first_beat_time = midi.estimate_beat_start()
    elif algorithm == "strict":
        if not midi.time_signature_changes:
            raise RuntimeError(
                "No time signature change event found. Unable to set beat "
                "start time using 'strict' algorithm.")
        midi.time_signature_changes.sort(key=lambda x: x.time)
        first_beat_time = midi.time_signature_changes[0].time
    elif algorithm == "custom":
        if first_beat_time is None:
            raise TypeError(
                "`first_beat_time` must be given when using 'custom' "
                "algorithm.")
        if first_beat_time < 0.0:
            raise ValueError("`first_beat_time` must be a positive number.")
    else:
        raise ValueError(
            "`algorithm` must be one of 'normal', 'strict' or 'custom'.")

    # get tempo change event times and contents
    tc_times, tempi = midi.get_tempo_changes()
    arg_sorted = np.argsort(tc_times)
    tc_times = tc_times[arg_sorted]
    tempi = tempi[arg_sorted]

    beat_times = midi.get_beats(first_beat_time)
    if not beat_times.size:
        raise ValueError("Cannot get beat timings to quantize the piano roll.")
    beat_times.sort()

    n_beats = len(beat_times)
    n_time_steps = resolution * n_beats

    # Parse downbeat array
    if not midi.time_signature_changes:
        downbeat = None
    else:
        downbeat = np.zeros((n_time_steps, 1), bool)
        downbeat[0] = True
        start = 0
        end = start
        for idx, tsc in enumerate(midi.time_signature_changes[:-1]):
            end += np.searchsorted(beat_times[end:],
                                   midi.time_signature_changes[idx + 1].time)
            start_idx = start * resolution
            end_idx = end * resolution
            stride = tsc.numerator * resolution
            downbeat[start_idx:end_idx:stride] = True
            start = end

    # Build tempo array
    one_more_beat = 2 * beat_times[-1] - beat_times[-2]
    beat_times_one_more = np.append(beat_times, one_more_beat)
    bpm = 60.0 / np.diff(beat_times_one_more)
    tempo = np.tile(bpm, (1, 24)).reshape(-1, 1)

    # Parse the tracks
    tracks = []
    for instrument in midi.instruments:
        if mode == "max":
            pianoroll = np.zeros((n_time_steps, 128), np.uint8)
        else:
            pianoroll = np.zeros((n_time_steps, 128), int)

        pitches = np.array([
            note.pitch for note in instrument.notes
            if note.end > first_beat_time
        ])
        note_on_times = np.array([
            note.start for note in instrument.notes
            if note.end > first_beat_time
        ])
        beat_indices = np.searchsorted(beat_times, note_on_times) - 1
        remained = note_on_times - beat_times[beat_indices]
        ratios = remained / (beat_times_one_more[beat_indices + 1] -
                             beat_times[beat_indices])
        rounded = np.round((beat_indices + ratios) * resolution)
        note_ons = rounded.astype(int)

        if collect_onsets_only:
            pianoroll[note_ons, pitches] = True
        elif instrument.is_drum:
            velocities = [
                note.velocity for note in instrument.notes
                if note.end > first_beat_time
            ]
            pianoroll[note_ons, pitches] = velocities
        else:
            note_off_times = np.array([
                note.end for note in instrument.notes
                if note.end > first_beat_time
            ])
            beat_indices = np.searchsorted(beat_times, note_off_times) - 1
            remained = note_off_times - beat_times[beat_indices]
            ratios = remained / (beat_times_one_more[beat_indices + 1] -
                                 beat_times[beat_indices])
            note_offs = ((beat_indices + ratios) * resolution).astype(int)

            for idx, start in enumerate(note_ons):
                end = note_offs[idx]
                velocity = instrument.notes[idx].velocity

                if velocity < 1:
                    continue

                if 0 < start < n_time_steps:
                    if pianoroll[start - 1, pitches[idx]]:
                        pianoroll[start - 1, pitches[idx]] = 0
                if end < n_time_steps - 1:
                    if pianoroll[end, pitches[idx]]:
                        end -= 1

                if mode == "max":
                    pianoroll[start:end, pitches[idx]] += velocity
                else:
                    maximum = np.maximum(pianoroll[start:end, pitches[idx]],
                                         velocity)
                    pianoroll[start:end, pitches[idx]] = maximum

        if mode == "max":
            track: Track = StandardTrack(
                name=str(instrument.name),
                program=int(instrument.program),
                is_drum=bool(instrument.is_drum),
                pianoroll=pianoroll,
            )
        else:
            track = Track(
                name=str(instrument.name),
                program=int(instrument.program),
                is_drum=bool(instrument.is_drum),
                pianoroll=pianoroll,
            )
        tracks.append(track)

    return Multitrack(resolution=resolution,
                      tempo=tempo,
                      downbeat=downbeat,
                      tracks=tracks)
Exemplo n.º 7
0
def _calc_matrices(
    midi_file_path: str,
    n_bars: int = N_BARS,
    q_note_res: int = Q_NOTE_RES,
    n_drums: int = N_DRUMS,
    midi_drum_map: Dict[int, int] = DEFAULT_MIDI_MAP,
    min_onsets: int = MIN_ONSETS,
    no_time_sig_means_44: bool = False
) -> (List[np.ndarray], List[np.ndarray]):
    matrix_total_notes = 4 * q_note_res * n_bars
    hop_size = 4 * q_note_res
    onset_matrices = []
    vel_matrices = []

    try:
        pm = PrettyMIDI(midi_file_path)
    except:
        log.error(f'Failed to load MIDI file. {midi_file_path}')
        return onset_matrices, vel_matrices

    if not pm.time_signature_changes and not no_time_sig_means_44:
        log.debug(f'MIDI file contains no time signatures. {midi_file_path}')
        return onset_matrices, vel_matrices

    if not pm.time_signature_changes:
        time_sigs = [(4, 4)]
    else:
        time_sigs = [(ts.numerator, ts.denominator)
                     for ts in pm.time_signature_changes]
    ts_nums, ts_denoms = zip(*time_sigs)

    # TODO(christhetree): genericize
    if not all([num == 4 for num in ts_nums]) \
            or not all([denom == 4 for denom in ts_denoms]):
        log.debug(f'MIDI file is not entirely 4/4. {midi_file_path}')
        return onset_matrices, vel_matrices

    if not pm.instruments:
        log.debug(
            f'MIDI file does not contain any instruments. {midi_file_path}')
        return onset_matrices, vel_matrices

    q_note_times = pm.get_beats()
    num_of_q_notes = len(q_note_times)
    midi_total_notes = num_of_q_notes * q_note_res
    ticks_per_note = pm.resolution / q_note_res

    if midi_total_notes < matrix_total_notes:
        log.debug(f'MIDI file is too short. {midi_file_path}')
        return onset_matrices, vel_matrices

    for instrument in pm.instruments:
        if instrument.is_drum:
            onset_m_s, vel_m_s = _calc_drum_matrices(
                pm, instrument, midi_total_notes, matrix_total_notes,
                ticks_per_note, hop_size, n_drums, midi_drum_map, min_onsets)
            onset_matrices.extend(onset_m_s)
            vel_matrices.extend(vel_m_s)

    assert len(onset_matrices) == len(vel_matrices)
    return onset_matrices, vel_matrices