<SYSTEM_TASK:>
Solve the following problem using Python, implementing the functions described below, one line at a time
<END_TASK>
<USER_TASK:>
Description:
 def match_note_onsets(ref_intervals, est_intervals, onset_tolerance=0.05, strict=False):
 """Compute a maximum matching between reference and estimated notes, only taking note onsets into account. Given two note sequences represented by ``ref_intervals`` and ``est_intervals`` (see :func:`mir_eval.io.load_valued_intervals`), we see the largest set of correspondences ``(i,j)`` such that the onset of reference note ``i`` is within ``onset_tolerance`` of the onset of estimated note ``j``. Every reference note is matched against at most one estimated note. Note there are separate functions :func:`match_note_offsets` and :func:`match_notes` for matching notes based on offsets only or based on onset, offset, and pitch, respectively. This is because the rules for matching note onsets and matching note offsets are different. Parameters ref_intervals : np.ndarray, shape=(n,2) Array of reference notes time intervals (onset and offset times) est_intervals : np.ndarray, shape=(m,2) Array of estimated notes time intervals (onset and offset times) onset_tolerance : float > 0 The tolerance for an estimated note's onset deviating from the reference note's onset, in seconds. Default is 0.05 (50 ms). strict : bool If ``strict=False`` (the default), threshold checks for onset matching are performed using ``<=`` (less than or equal). If ``strict=True``, the threshold checks are performed using ``<`` (less than). Returns ------- matching : list of tuples A list of matched reference and estimated notes. ``matching[i] == (i, j)`` where reference note ``i`` matches estimated note ``j``. """

    # set the comparison function
    if strict:
        cmp_func = np.less
    else:
        cmp_func = np.less_equal

    # check for onset matches
    onset_distances = np.abs(np.subtract.outer(ref_intervals[:, 0],
                                               est_intervals[:, 0]))
    # Round distances to a target precision to avoid the situation where
    # if the distance is exactly 50ms (and strict=False) it erroneously
    # doesn't match the notes because of precision issues.
    onset_distances = np.around(onset_distances, decimals=N_DECIMALS)
    onset_hit_matrix = cmp_func(onset_distances, onset_tolerance)

    # find hits
    hits = np.where(onset_hit_matrix)

    # Construct the graph input
    # Flip graph so that 'matching' is a list of tuples where the first item
    # in each tuple is the reference note index, and the second item is the
    # estimated note index.
    G = {}
    for ref_i, est_i in zip(*hits):
        if est_i not in G:
            G[est_i] = []
        G[est_i].append(ref_i)

    # Compute the maximum matching
    matching = sorted(util._bipartite_match(G).items())

    return matching