import torch


def vtln_warp_freq(vtln_low_cutoff, vtln_high_cutoff, low_freq, high_freq, vtln_warp_factor, freq):
    """
    This computes a VTLN warping function that is not the same as HTK's one, but has similar inputs (this function has
    the advantage of never producing empty bins).

    This function computes a warp function F(freq), defined between low_freq and high_freq inclusive, with the following
    properties:
        F(low_freq) == low_freq
        F(high_freq) == high_freq
    The function is continuous and piecewise linear with two inflection points.
    The lower inflection point (measured in terms of the unwarped frequency) is at frequency l, determined as described
    below. The higher inflection point is at a frequency h, determined as described below.
    If l <= f <= h, then F(f) = f/vtln_warp_factor.
    If the higher inflection point (measured in terms of the unwarped frequency) is at h, then max(h, F(h)) ==
    vtln_high_cutoff. Since (by the last point) F(h) == h/vtln_warp_factor, then max(h, h/vtln_warp_factor) ==
    vtln_high_cutoff, so
        h = vtln_high_cutoff / max(1, 1/vtln_warp_factor) = vtln_high_cutoff * min(1, vtln_warp_factor).
    If the lower inflection point (measured in terms of the unwarped frequency) is at l, then min(l, F(l)) ==
    vtln_low_cutoff. This implies that
        l = vtln_low_cutoff / min(1, 1/vtln_warp_factor) = vtln_low_cutoff * max(1, vtln_warp_factor)

    Args:
        vtln_low_cutoff (float): Lower frequency cutoffs for VTLN
        vtln_high_cutoff (float): Upper frequency cutoffs for VTLN
        low_freq (float): Lower frequency cutoffs in mel computation
        high_freq (float): Upper frequency cutoffs in mel computation
        vtln_warp_factor (float): Vtln warp factor
        freq (Tensor): given frequency in Hz
    Returns:
        Tensor: Freq after vtln warp
    """
    assert vtln_low_cutoff > low_freq, 'be sure to set the vtln_low option higher than low_freq'
    assert vtln_high_cutoff < high_freq, 'be sure to set the vtln_high option lower than high_freq [or negative]'
    low = vtln_low_cutoff * max(1.0, vtln_warp_factor)
    high = vtln_high_cutoff * min(1.0, vtln_warp_factor)
    scale = 1.0 / vtln_warp_factor
    f_low = scale * low  # F(l)
    f_high = scale * high  # F(h)
    assert low > low_freq and high < high_freq
    # slope of left part of the 3-piece linear function
    scale_left = (f_low - low_freq) / (low - low_freq)
    # [slope of center part is just "scale"]
    # slope of right part of the 3-piece linear function
    scale_right = (high_freq - f_high) / (high_freq - high)
    res = torch.empty_like(freq)
    outside_low_high_freq = torch.lt(freq, low_freq) | torch.gt(freq, high_freq)  # freq < low_freq || freq > high_freq
    before_l = torch.lt(freq, low)  # freq < l
    before_h = torch.lt(freq, high)  # freq < h
    after_h = torch.ge(freq, high)  # freq >= h
    # order of operations matter here (since there is overlapping frequency regions)
    res[after_h] = high_freq + scale_right * (freq[after_h] - high_freq)
    res[before_h] = scale * freq[before_h]
    res[before_l] = low_freq + scale_left * (freq[before_l] - low_freq)
    res[outside_low_high_freq] = freq[outside_low_high_freq]
    return res