algorithms/asa.py
def _match_fronts(onset_fronts, offset_fronts, onsets, offsets, debug=False):
    """
    Returns a segmentation mask, which looks like this:
    frequency 1: 0 0 4 4 4 4 4 0 0 5 5 5
    frequency 2: 0 4 4 4 4 4 0 0 0 0 5 5
    frequency 3: 0 4 4 4 4 4 4 4 5 5 5 5

    That is, each item in the array is either a 0 (not part of a segment) or a positive
    integer which indicates which segment the sample in that frequency band belongs to.
    """
    def printd(*args, **kwargs):
        if debug:
            print(*args, **kwargs)

    # Make copies of everything, so we can do whatever we want with them
    onset_fronts = np.copy(onset_fronts)
    offset_fronts = np.copy(offset_fronts)
    onsets = np.copy(onsets)
    offsets = np.copy(offsets)

    # This is what we will return
    segmentation_mask = np.zeros_like(onset_fronts)

    # - Take the first frequency in the onset_fronts matrix
    #     [ s s s s s s s s s] <-- This frequency
    #     [ s s s s s s s s s]
    #     [ s s s s s s s s s]
    #     [ s s s s s s s s s]
    #     [ s s s s s s s s s]

    # - Follow it along in time like this:

    #     first sample    last sample
    #       v      -->      v
    #     [ s s s s s s s s s]
    #     [ s s s s s s s s s]
    #     [ s s s s s s s s s]
    #     [ s s s s s s s s s]
    #     [ s s s s s s s s s]

    # until you get to the first onset front in that frequency

    #     Here it is!
    #         v
    #     [ . O . . . . . . .]
    #     [ . . O . . . . . .]
    #     [ . . O . . . . . .]
    #     [ . O . . . . . . .]
    #     [ O . . . . . . . .]

    resulting_onset_fronts = np.copy(onset_fronts)
    printd("    -> Dealing with onset fronts...")
    for onset_front_id in _get_front_ids_one_at_a_time(onset_fronts):
        printd("      -> Dealing with onset front", int(onset_front_id))
        front_is_complete = False
        while not front_is_complete:
            # - Now, starting at this onset front in each frequency, find that onset's corresponding offset

            #     [ . O . . . . F . .]
            #     [ . . O . . . F . .]
            #     [ . . O . F . . . .]
            #     [ . O . F . . . . .]
            #     [ O F . . . . . . .]

            corresponding_offsets = _get_corresponding_offsets(resulting_onset_fronts, onset_front_id, onsets, offsets)

            # It is possible that onset_front_id has been removed from resulting_onset_fronts,
            # if so, skip it and move on to the next onset front (we are iterating over the original
            # to keep the iterator valid)
            if not corresponding_offsets:
                break

            # - Get all the offset fronts that are composed of at least one of these offset times

            #     [ . O . . . . 1 . .]
            #     [ . . O 3 . . 1 . .]
            #     [ . . O 3 F . 1 . .]
            #     [ . O . 3 . . . 1 .]
            #     [ O F 3 . . . . . .]

            _all_offset_fronts_of_interest, ids_ntimes_seen = _get_all_offset_fronts_from_offsets(offset_fronts, corresponding_offsets)

            # - Check how many of these offset times each of the offset fronts are composed of:

            #     [ . O . . . . Y . .]
            #     [ . . O 3 . . Y . .]
            #     [ . . O 3 F . 1 . .]
            #     [ . O . X . . . 1 .]
            #     [ O F 3 . . . . . .]

            # In this example, offset front 1 is made up of 4 offset times, 2 of which (the Y's) are offset times
            # that correspond to onsets in the onset front we are currently dealing with. Meanwhile, offset
            # front 3 is made up of 4 offset times, only one of which (the X) is one of the offsets that corresponds
            # to the onset front.

            # - Choose the offset front which matches the most offset time candidates. In this example, offset front 1
            #   is chosen because it has 2 of these offset times.
            #   If there is a tie, we choose the ID with the lower number
            ntimes_seen_sorted = sorted([(k, v) for k, v in ids_ntimes_seen.items()], key=lambda tup: (-1 * tup[1], tup[0]))
            assert len(ntimes_seen_sorted) > 0, "We somehow got an empty dict of offset front IDs"

            # Only use the special final front (the -1, catch-all front composed of final samples in each frequency) if necessary
            offset_front_id, _ntimes_seen = ntimes_seen_sorted[0]
            if offset_front_id == -1 and len(ntimes_seen_sorted) > 1:
                offset_front_id, _ntimes_seen = ntimes_seen_sorted[1]
            offset_front_id_most_overlap = offset_front_id

            # - Finally, update the segmentation mask to follow the offset
            #   front from where it first overlaps in frequency with the onset front to where it ends or to where
            #   the onset front ends, whichever happens first.

            #     [ . S S S S S S . .]
            #     [ . . S S S S S . .]
            #     [ . . S S S S S . .]
            #     [ . S S S S S S S .]
            #     [ O F 3 . . . . . .]  <-- This frequency has not yet been matched with an offset front
            front_is_complete = _update_segmentation_mask(segmentation_mask,
                                                            resulting_onset_fronts,
                                                            offset_fronts,
                                                            onset_front_id,
                                                            offset_front_id_most_overlap)

            # Remove any onsets that are covered by the new segmentation mask
            _remove_overlaps(segmentation_mask, resulting_onset_fronts)

            # Remove any offsets that are covered by the new segmentaion mask
            _remove_overlaps(segmentation_mask, offset_fronts)

            # - Repeat this algorithm, restarting in the first frequency channel that did not match (the last frequency in
            #   the above example). Do this until you have finished with this onset front.

        # - Repeat for each onset front in the rest of this frequency
        # - Repeat for each frequency

    return segmentation_mask
def _match_fronts(onset_fronts, offset_fronts, onsets, offsets, debug=False):
    """
    Returns a segmentation mask, which looks like this:
    frequency 1: 0 0 4 4 4 4 4 0 0 5 5 5
    frequency 2: 0 4 4 4 4 4 0 0 0 0 5 5
    frequency 3: 0 4 4 4 4 4 4 4 5 5 5 5

    That is, each item in the array is either a 0 (not part of a segment) or a positive
    integer which indicates which segment the sample in that frequency band belongs to.
    """
    def printd(*args, **kwargs):
        if debug:
            print(*args, **kwargs)

    # Make copies of everything, so we can do whatever we want with them
    onset_fronts = np.copy(onset_fronts)
    offset_fronts = np.copy(offset_fronts)
    onsets = np.copy(onsets)
    offsets = np.copy(offsets)

    # This is what we will return
    segmentation_mask = np.zeros_like(onset_fronts)

    # - Take the first frequency in the onset_fronts matrix
    #     [ s s s s s s s s s] <-- This frequency
    #     [ s s s s s s s s s]
    #     [ s s s s s s s s s]
    #     [ s s s s s s s s s]
    #     [ s s s s s s s s s]

    # - Follow it along in time like this:

    #     first sample    last sample
    #       v      -->      v
    #     [ s s s s s s s s s]
    #     [ s s s s s s s s s]
    #     [ s s s s s s s s s]
    #     [ s s s s s s s s s]
    #     [ s s s s s s s s s]

    # until you get to the first onset front in that frequency

    #     Here it is!
    #         v
    #     [ . O . . . . . . .]
    #     [ . . O . . . . . .]
    #     [ . . O . . . . . .]
    #     [ . O . . . . . . .]
    #     [ O . . . . . . . .]

    resulting_onset_fronts = np.copy(onset_fronts)
    printd("    -> Dealing with onset fronts...")
    for onset_front_id in _get_front_ids_one_at_a_time(onset_fronts):
        printd("      -> Dealing with onset front", int(onset_front_id))
        front_is_complete = False
        while not front_is_complete:
            # - Now, starting at this onset front in each frequency, find that onset's corresponding offset

            #     [ . O . . . . F . .]
            #     [ . . O . . . F . .]
            #     [ . . O . F . . . .]
            #     [ . O . F . . . . .]
            #     [ O F . . . . . . .]

            corresponding_offsets = _get_corresponding_offsets(resulting_onset_fronts, onset_front_id, onsets, offsets)

            # It is possible that onset_front_id has been removed from resulting_onset_fronts,
            # if so, skip it and move on to the next onset front (we are iterating over the original
            # to keep the iterator valid)
            if not corresponding_offsets:
                break

            # - Get all the offset fronts that are composed of at least one of these offset times

            #     [ . O . . . . 1 . .]
            #     [ . . O 3 . . 1 . .]
            #     [ . . O 3 F . 1 . .]
            #     [ . O . 3 . . . 1 .]
            #     [ O F 3 . . . . . .]

            _all_offset_fronts_of_interest, ids_ntimes_seen = _get_all_offset_fronts_from_offsets(offset_fronts, corresponding_offsets)

            # - Check how many of these offset times each of the offset fronts are composed of:

            #     [ . O . . . . Y . .]
            #     [ . . O 3 . . Y . .]
            #     [ . . O 3 F . 1 . .]
            #     [ . O . X . . . 1 .]
            #     [ O F 3 . . . . . .]

            # In this example, offset front 1 is made up of 4 offset times, 2 of which (the Y's) are offset times
            # that correspond to onsets in the onset front we are currently dealing with. Meanwhile, offset
            # front 3 is made up of 4 offset times, only one of which (the X) is one of the offsets that corresponds
            # to the onset front.

            # - Choose the offset front which matches the most offset time candidates. In this example, offset front 1
            #   is chosen because it has 2 of these offset times.
            #   If there is a tie, we choose the ID with the lower number
            ntimes_seen_sorted = sorted([(k, v) for k, v in ids_ntimes_seen.items()], key=lambda tup: (-1 * tup[1], tup[0]))
            assert len(ntimes_seen_sorted) > 0, "We somehow got an empty dict of offset front IDs"

            # Only use the special final front (the -1, catch-all front composed of final samples in each frequency) if necessary
            offset_front_id, _ntimes_seen = ntimes_seen_sorted[0]
            if offset_front_id == -1 and len(ntimes_seen_sorted) > 1:
                offset_front_id, _ntimes_seen = ntimes_seen_sorted[1]
            offset_front_id_most_overlap = offset_front_id

            # - Finally, update the segmentation mask to follow the offset
            #   front from where it first overlaps in frequency with the onset front to where it ends or to where
            #   the onset front ends, whichever happens first.

            #     [ . S S S S S S . .]
            #     [ . . S S S S S . .]
            #     [ . . S S S S S . .]
            #     [ . S S S S S S S .]
            #     [ O F 3 . . . . . .]  <-- This frequency has not yet been matched with an offset front
            front_is_complete = _update_segmentation_mask(segmentation_mask,
                                                            resulting_onset_fronts,
                                                            offset_fronts,
                                                            onset_front_id,
                                                            offset_front_id_most_overlap)

            # Remove any onsets that are covered by the new segmentation mask
            _remove_overlaps(segmentation_mask, resulting_onset_fronts)

            # Remove any offsets that are covered by the new segmentaion mask
            _remove_overlaps(segmentation_mask, offset_fronts)

            # - Repeat this algorithm, restarting in the first frequency channel that did not match (the last frequency in
            #   the above example). Do this until you have finished with this onset front.

        # - Repeat for each onset front in the rest of this frequency
        # - Repeat for each frequency

    return segmentation_mask
def _match_fronts ( onset_fronts , offset_fronts , onsets , offsets , debug = False ) : def printd ( * args , * * kwargs ) : if debug : print ( * args , * * kwargs ) # Make copies of everything, so we can do whatever we want with them onset_fronts = np . copy ( onset_fronts ) offset_fronts = np . copy ( offset_fronts ) onsets = np . copy ( onsets ) offsets = np . copy ( offsets ) # This is what we will return segmentation_mask = np . zeros_like ( onset_fronts ) # - Take the first frequency in the onset_fronts matrix #     [ s s s s s s s s s] <-- This frequency #     [ s s s s s s s s s] #     [ s s s s s s s s s] #     [ s s s s s s s s s] #     [ s s s s s s s s s] # - Follow it along in time like this: #     first sample    last sample #       v      -->      v #     [ s s s s s s s s s] #     [ s s s s s s s s s] #     [ s s s s s s s s s] #     [ s s s s s s s s s] #     [ s s s s s s s s s] # until you get to the first onset front in that frequency #     Here it is! #         v #     [ . O . . . . . . .] #     [ . . O . . . . . .] #     [ . . O . . . . . .] #     [ . O . . . . . . .] #     [ O . . . . . . . .] resulting_onset_fronts = np . copy ( onset_fronts ) printd ( "    -> Dealing with onset fronts..." ) for onset_front_id in _get_front_ids_one_at_a_time ( onset_fronts ) : printd ( "      -> Dealing with onset front" , int ( onset_front_id ) ) front_is_complete = False while not front_is_complete : # - Now, starting at this onset front in each frequency, find that onset's corresponding offset #     [ . O . . . . F . .] #     [ . . O . . . F . .] #     [ . . O . F . . . .] #     [ . O . F . . . . .] #     [ O F . . . . . . .] corresponding_offsets = _get_corresponding_offsets ( resulting_onset_fronts , onset_front_id , onsets , offsets ) # It is possible that onset_front_id has been removed from resulting_onset_fronts, # if so, skip it and move on to the next onset front (we are iterating over the original # to keep the iterator valid) if not corresponding_offsets : break # - Get all the offset fronts that are composed of at least one of these offset times #     [ . O . . . . 1 . .] #     [ . . O 3 . . 1 . .] #     [ . . O 3 F . 1 . .] #     [ . O . 3 . . . 1 .] #     [ O F 3 . . . . . .] _all_offset_fronts_of_interest , ids_ntimes_seen = _get_all_offset_fronts_from_offsets ( offset_fronts , corresponding_offsets ) # - Check how many of these offset times each of the offset fronts are composed of: #     [ . O . . . . Y . .] #     [ . . O 3 . . Y . .] #     [ . . O 3 F . 1 . .] #     [ . O . X . . . 1 .] #     [ O F 3 . . . . . .] # In this example, offset front 1 is made up of 4 offset times, 2 of which (the Y's) are offset times # that correspond to onsets in the onset front we are currently dealing with. Meanwhile, offset # front 3 is made up of 4 offset times, only one of which (the X) is one of the offsets that corresponds # to the onset front. # - Choose the offset front which matches the most offset time candidates. In this example, offset front 1 #   is chosen because it has 2 of these offset times. #   If there is a tie, we choose the ID with the lower number ntimes_seen_sorted = sorted ( [ ( k , v ) for k , v in ids_ntimes_seen . items ( ) ] , key = lambda tup : ( - 1 * tup [ 1 ] , tup [ 0 ] ) ) assert len ( ntimes_seen_sorted ) > 0 , "We somehow got an empty dict of offset front IDs" # Only use the special final front (the -1, catch-all front composed of final samples in each frequency) if necessary offset_front_id , _ntimes_seen = ntimes_seen_sorted [ 0 ] if offset_front_id == - 1 and len ( ntimes_seen_sorted ) > 1 : offset_front_id , _ntimes_seen = ntimes_seen_sorted [ 1 ] offset_front_id_most_overlap = offset_front_id # - Finally, update the segmentation mask to follow the offset #   front from where it first overlaps in frequency with the onset front to where it ends or to where #   the onset front ends, whichever happens first. #     [ . S S S S S S . .] #     [ . . S S S S S . .] #     [ . . S S S S S . .] #     [ . S S S S S S S .] #     [ O F 3 . . . . . .]  <-- This frequency has not yet been matched with an offset front front_is_complete = _update_segmentation_mask ( segmentation_mask , resulting_onset_fronts , offset_fronts , onset_front_id , offset_front_id_most_overlap ) # Remove any onsets that are covered by the new segmentation mask _remove_overlaps ( segmentation_mask , resulting_onset_fronts ) # Remove any offsets that are covered by the new segmentaion mask _remove_overlaps ( segmentation_mask , offset_fronts ) # - Repeat this algorithm, restarting in the first frequency channel that did not match (the last frequency in #   the above example). Do this until you have finished with this onset front. # - Repeat for each onset front in the rest of this frequency # - Repeat for each frequency return segmentation_mask
Returns a segmentation mask, which looks like this:
    frequency 1: 0 0 4 4 4 4 4 0 0 5 5 5
    frequency 2: 0 4 4 4 4 4 0 0 0 0 5 5
    frequency 3: 0 4 4 4 4 4 4 4 5 5 5 5

    That is, each item in the array is either a 0 (not part of a segment) or a positive
    integer which indicates which segment the sample in that frequency band belongs to.
Returns a segmentation mask which looks like this : frequency 1 : 0 0 4 4 4 4 4 0 0 5 5 5 frequency 2 : 0 4 4 4 4 4 0 0 0 0 5 5 frequency 3 : 0 4 4 4 4 4 4 4 5 5 5 5