<commit_before># CREATED:2014-03-07 by <NAME> <<EMAIL>>
'''
Melody extraction evaluation, based on the protocols used in MIREX since 2005.

For a detailed explanation of the measures please refer to:
<NAME>, <NAME>, <NAME> and <NAME>, "Melody Extraction
from Polyphonic Music Signals: Approaches, Applications and Challenges",
IEEE Signal Processing Magazine, 31(2):118-134, Mar. 2014.
'''

import numpy as np
import sys
import scipy.interpolate


def hz2cents(freq_hz, base_frequency=10.0):
    '''
    Convert an array of frequency values in Hz to cents
    0 values are left in place.

    :parameters:
        - freq_hz : ndarray
            Array of frequencies in Hz.
        - base_frequency : float
            Base frequency for conversion.
    :returns:
        - cent : ndarray
            Array of frequencies in cents, relative to base_frequency
    '''
    freq_cent = np.zeros(freq_hz.shape[0])
    freq_nonz_ind = np.flatnonzero(freq_hz)
    freq_cent[freq_nonz_ind] = 1200*np.log2(np.abs(freq_hz[freq_nonz_ind])/base_frequency)

    return freq_cent


def freq_to_voicing(frequencies):
    '''Convert from an array of frequency values to frequency array + voice/unvoiced array

    :parameters:
        - frequencies : ndarray
            Array of frequencies.  A frequency <= 0 indicates "unvoiced".
    :returns:
        - frequencies : ndarray
            Array of frequencies, all >= 0.
        - voiced : ndarray
            Boolean array, same length as frequencies, which indicates voiced or unvoiced
    '''
    return np.abs(frequencies), frequencies > 0


def resample_melody_series(times, frequencies, voicing, hop=0.01):
    '''Resamples frequency and voicing time series to a new timescale.
    Maintains any zero ("unvoiced") values in frequencies.

    :parameters:
        - times : ndarray
            Times of each frequency value
        - frequencies : ndarray
            Array of frequency values, >= 0
        - voicing : ndarray
            Boolean array which indicates voiced or unvoiced
        - hop : float
            Hop size for resampling.  Default .01

    :returns:
        - times_new : ndarray
            Times of each resampled frequency value
        - frequencies_resampled : ndarray
            Frequency array resampled to new timebase
        - voicing_resampled : ndarray
            Boolean voicing array resampled to new timebase
    '''
    # Fill in zero values with the last reported frequency
    # to avoid erroneous values when resampling
    frequencies_held = np.array(frequencies)
    for n, frequency in enumerate(frequencies[1:]):
        if frequency == 0:
            frequencies_held[n + 1] = frequencies_held[n]
    # Compute new timebase.  Rounding/linspace is to avoid float problems.
    times = np.round(times*1e10)*1e-10
    times_new = np.linspace(0, hop*int(np.floor(times[-1]/hop)), int(np.floor(times[-1]/hop)) + 1)
    times_new = np.round(times_new*1e10)*1e-10
    # Linearly interpolate frequencies
    frequencies_resampled = scipy.interpolate.interp1d(times, frequencies_held)(times_new)
    # Retain zeros
    frequency_mask = scipy.interpolate.interp1d(times, frequencies, 'zero')(times_new)
    frequencies_resampled *= (frequency_mask != 0)
    # Nearest-neighbor interpolate voicing
    voicing_resampled = scipy.interpolate.interp1d(times, voicing, 'zero')(times_new)
    return times_new, frequencies_resampled, voicing_resampled.astype(np.bool)

def to_cent_voicing(ref_time, ref_freq, est_time, est_freq, **kwargs):
    '''Converts reference and estimated time/frequency (Hz) annotations to
    sampled frequency (cent)/voicing arrays.

    A zero frequency indicates "unvoiced".

    A negative frequency indicates "Predicted as unvoiced,
    but if it's voiced, this is the frequency estimate".

    :parameters:
        - ref_time : ndarray
            Time of each reference frequency value
        - ref_freq : ndarray
            Array of reference frequency values
        - est_time : ndarray
            Time of each reference frequency value
        - est_freq : ndarray
            Array of reference frequency values
        - base_frequency : float
            Base frequency in Hz for conversion to cents, default 10.0
        - hop : float
            Hop size, in seconds, to resample, default .01

    :returns:
        - ref_voicing : ndarray
            Resampled reference boolean voicing array
        - est_voicing : ndarray
            Resampled estimated boolean voicing array
        - ref_cent : ndarray
            Resampled reference frequency (cent) array
        - est_cent : ndarray
            Resampled estimated frequency (cent) array
    '''
    # Set default kwargs parameters
    base_frequency = kwargs.get('base_frequency', 10.)
    hop = kwargs.get('hop', .01)
    # Check if missing sample at time 0 and if so add one
    if ref_time[0] > 0:
        ref_time = np.insert(ref_time, 0, 0)
        ref_freq = np.insert(ref_freq, 0, ref_voicing[0])
    if est_time[0] > 0:
        est_time = np.insert(est_time, 0, 0)
        est_freq = np.insert(est_freq, 0, est_voicing[0])
    # Get separated frequency array and voicing boolean array
    ref_freq, ref_voicing = freq_to_voicing(ref_freq)
    est_freq, est_voicing = freq_to_voicing(est_freq)
    # convert both sequences to cents
    ref_cent = hz2cents(ref_freq)
    est_cent = hz2cents(est_freq)
    # Resample to common time base
    ref_time_grid, ref_cent, ref_voicing = resample_melody_series(ref_time,
                                                                  ref_cent,
                                                                  ref_voicing,
                                                                  hop)<commit_msg>Merging, to include Justin's bugfix<commit_after># CREATED:2014-03-07 by <NAME> <<EMAIL>>
'''
Melody extraction evaluation, based on the protocols used in MIREX since 2005.

For a detailed explanation of the measures please refer to:
<NAME>, <NAME>, <NAME> and <NAME>, "Melody Extraction
from Polyphonic Music Signals: Approaches, Applications and Challenges",
IEEE Signal Processing Magazine, 31(2):118-134, Mar. 2014.
'''

import numpy as np
import sys
import scipy.interpolate


def hz2cents(freq_hz, base_frequency=10.0):
    '''
    Convert an array of frequency values in Hz to cents
    0 values are left in place.

    :parameters:
        - freq_hz : ndarray
            Array of frequencies in Hz.
        - base_frequency : float
            Base frequency for conversion.
    :returns:
        - cent : ndarray
            Array of frequencies in cents, relative to base_frequency
    '''
    freq_cent = np.zeros(freq_hz.shape[0])
    freq_nonz_ind = np.flatnonzero(freq_hz)
    freq_cent[freq_nonz_ind] = 1200*np.log2(np.abs(freq_hz[freq_nonz_ind])/base_frequency)

    return freq_cent


def freq_to_voicing(frequencies):
    '''Convert from an array of frequency values to frequency array + voice/unvoiced array

    :parameters:
        - frequencies : ndarray
            Array of frequencies.  A frequency <= 0 indicates "unvoiced".
    :returns:
        - frequencies : ndarray
            Array of frequencies, all >= 0.
        - voiced : ndarray
            Boolean array, same length as frequencies, which indicates voiced or unvoiced
    '''
    return np.abs(frequencies), frequencies > 0


def resample_melody_series(times, frequencies, voicing, hop=0.01):
    '''Resamples frequency and voicing time series to a new timescale.
    Maintains any zero ("unvoiced") values in frequencies.

    :parameters:
        - times : ndarray
            Times of each frequency value
        - frequencies : ndarray
            Array of frequency values, >= 0
        - voicing : ndarray
            Boolean array which indicates voiced or unvoiced
        - hop : float
            Hop size for resampling.  Default .01

    :returns:
        - times_new : ndarray
            Times of each resampled frequency value
        - frequencies_resampled : ndarray
            Frequency array resampled to new timebase
        - voicing_resampled : ndarray
            Boolean voicing array resampled to new timebase
    '''
    # Fill in zero values with the last reported frequency
    # to avoid erroneous values when resampling
    frequencies_held = np.array(frequencies)
    for n, frequency in enumerate(frequencies[1:]):
        if frequency == 0:
            frequencies_held[n + 1] = frequencies_held[n]
    # Compute new timebase.  Rounding/linspace is to avoid float problems.
    times = np.round(times*1e10)*1e-10
    times_new = np.linspace(0, hop*int(np.floor(times[-1]/hop)), int(np.floor(times[-1]/hop)) + 1)
    times_new = np.round(times_new*1e10)*1e-10
    # Linearly interpolate frequencies
    frequencies_resampled = scipy.interpolate.interp1d(times, frequencies_held)(times_new)
    # Retain zeros
    frequency_mask = scipy.interpolate.interp1d(times, frequencies, 'zero')(times_new)
    frequencies_resampled *= (frequency_mask != 0)
    # Nearest-neighbor interpolate voicing
    voicing_resampled = scipy.interpolate.interp1d(times, voicing, 'zero')(times_new)
    return times_new, frequencies_resampled, voicing_resampled.astype(np.bool)

def to_cent_voicing(ref_time, ref_freq, est_time, est_freq, **kwargs):
    '''Converts reference and estimated time/frequency (Hz) annotations to
    sampled frequency (cent)/voicing arrays.

    A zero frequency indicates "unvoiced".

    A negative frequency indicates "Predicted as unvoiced,
    but if it's voiced, this is the frequency estimate".

    :parameters:
        - ref_time : ndarray
            Time of each reference frequency value
        - ref_freq : ndarray
            Array of reference frequency values
        - est_time : ndarray
            Time of each reference frequency value
        - est_freq : ndarray
            Array of reference frequency values
        - base_frequency : float
            Base frequency in Hz for conversion to cents, default 10.0
        - hop : float
            Hop size, in seconds, to resample, default .01

    :returns:
        - ref_voicing : ndarray
            Resampled reference boolean voicing array
        - est_voicing : ndarray
            Resampled estimated boolean voicing array
        - ref_cent : ndarray
            Resampled reference frequency (cent) array
        - est_cent : ndarray
            Resampled estimated frequency (cent) array
    '''
    # Set default kwargs parameters
    base_frequency = kwargs.get('base_frequency', 10.)
    hop = kwargs.get('hop', .01)
    # Check if missing sample at time 0 and if so add one
    if ref_time[0] > 0:
        ref_time = np.insert(ref_time, 0, 0)
        ref_freq = np.insert(ref_freq, 0, ref_freq[0])
    if est_time[0] > 0:
        est_time = np.insert(est_time, 0, 0)
        est_freq = np.insert(est_freq, 0, est_freq[0])
    # Get separated frequency array and voicing boolean array
    ref_freq, ref_voicing = freq_to_voicing(ref_freq)
    est_freq, est_voicing = freq_to_voicing(est_freq)
    # convert both sequences to cents
    ref_cent = hz2cents(ref_freq)
    est_cent = hz2cents(est_freq)
    # Resample to common time base
    ref_time_grid, ref_cent, ref_voicing = resample_melody_series(ref_time,
                                                                  ref_cent,
                                                                  ref_voicing,
                                                                  hop)
{'fraction_non_alphanumeric': 0.07569166521663477, 'fraction_numerical': 0.01600835218374804, 'mean_word_length': 2.4571428571428573, 'pattern_counts': {'":': 0, '<': 19, '<?xml version=': 0, '>': 27, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 12, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7417124', 'n_tokens_mistral': 3434, 'n_tokens_neox': 3256, 'n_words': 1025}