Calculate filter kernel from attenuation vector.
    Attenuation vector should represent magnitude frequency response of system
    
    :param genrate: The generation samplerate at which the test signal was played
    :type genrate: int
    :param fresponse: Frequency response of the system in dB, i.e. relative attenuations of frequencies
    :type fresponse: numpy.ndarray
    :param frequencies: corresponding frequencies for the fresponse
    :type frequencies: numpy.ndarray
    :param frange: the min and max frequencies for which the filter kernel will affect
    :type frange: (int, int)
    :param filter_len: the desired length for the resultant impulse response
    :type filter_len: int
    :param db: whether the fresponse given is the a vector of multiplication or decibel factors
    :type db: bool
    :returns: numpy.ndarray -- the impulse response

def impulse_response(genrate, fresponse, frequencies, frange, filter_len=2 ** 14, db=True):
    """
    Calculate filter kernel from attenuation vector.
    Attenuation vector should represent magnitude frequency response of system
    
    :param genrate: The generation samplerate at which the test signal was played
    :type genrate: int
    :param fresponse: Frequency response of the system in dB, i.e. relative attenuations of frequencies
    :type fresponse: numpy.ndarray
    :param frequencies: corresponding frequencies for the fresponse
    :type frequencies: numpy.ndarray
    :param frange: the min and max frequencies for which the filter kernel will affect
    :type frange: (int, int)
    :param filter_len: the desired length for the resultant impulse response
    :type filter_len: int
    :param db: whether the fresponse given is the a vector of multiplication or decibel factors
    :type db: bool
    :returns: numpy.ndarray -- the impulse response
    """

    freq = frequencies
    max_freq = genrate / 2 + 1

    attenuations = np.zeros_like(fresponse)
    # add extra points for windowing
    winsz = 0.05  # percent
    lowf = max(0, frange[0] - (frange[1] - frange[0]) * winsz)
    highf = min(frequencies[-1], frange[1] + (frange[1] - frange[0]) * winsz)

    f0 = (np.abs(freq - lowf)).argmin()
    f1 = (np.abs(freq - highf)).argmin()
    fmax = (np.abs(freq - max_freq)).argmin() + 1
    attenuations[f0:f1] = fresponse[f0:f1] * tukey(len(fresponse[f0:f1]), winsz)
    if db:
        freq_response = 10 ** ((attenuations).astype(float) / 20)
    else:
        freq_response = attenuations

    freq_response = freq_response[:fmax]

    impulse_response = np.fft.irfft(freq_response)

    # rotate to create causal filter, and truncate
    impulse_response = np.roll(impulse_response, len(impulse_response) // 2)

    # truncate
    if filter_len < len(impulse_response):
        startidx = (len(impulse_response) // 2) - (filter_len // 2)
        stopidx = (len(impulse_response) // 2) + (filter_len // 2)
        impulse_response = impulse_response[startidx:stopidx]

    # should I also window the impulse response - by how much?
    impulse_response = impulse_response * tukey(len(impulse_response), 0.05)

    return impulse_response