def get_df_excerpt(note_df, start_time, end_time):
    """
    Return an excerpt of the given note_df, with notes cut at the given
    start and end times.

    Parameters
    ----------
    note_df : pd.DataFrame
        The note_df we want to take an excerpt of.

    start_time : int
        The start time for the returned excerpt, in ms, inclusive. Notes
        entirely before this time will be dropped. Notes which onset before
        this time but continue after it will have their onset shifted to
        this time.

    end_time : int
        The end time for the returned excerpt, in ms, exclusive. Notes
        entirely after this time will be dropped. Notes which onset before
        this time but continue after it will have their offset shifted to
        this time. None to enforce no end time.

    Returns
    -------
    note_df : pd.DataFrame
        An excerpt of the notes from the given note_df, within the given
        two times.
    """
    # Make copy so as not to change original values
    note_df = note_df.copy()

    # Move onsets of notes which lie before start (and finish after start)
    need_to_shift = (note_df.onset < start_time) & (
        note_df.onset + note_df.dur > start_time
    )
    shift_amt = start_time - note_df.loc[need_to_shift, "onset"]
    note_df.loc[need_to_shift, "onset"] = start_time
    note_df.loc[need_to_shift, "dur"] -= shift_amt

    # Shorten notes which go past end time
    if end_time is not None:
        need_to_shorten = (note_df.onset < end_time) & (
            note_df.onset + note_df.dur > end_time
        )
        note_df.loc[need_to_shorten, "dur"] = (
            end_time - note_df.loc[need_to_shorten, "onset"]
        )

    # Drop notes which lie outside of bounds
    to_keep = note_df.onset >= start_time
    if end_time is not None:
        to_keep &= note_df.onset < end_time
    note_df = note_df.loc[to_keep]
    return note_df