Source code for pyfar.constants.frequency_weighting

"""File containing all frequency weighting functions."""
from typing import Literal, Union
import numpy as np
import pyfar as pf

# Constants for the weighting curve formulas.
# See appendix E in IEC 61672-1.
_F_1 = 20.598997057618316
_F_2 = 107.65264864304629
_F_3 = 737.8622307362901
_F_4 = 12194.217147998012
_A_1000 = -2
_C_1000 = -0.062

# Constants for nominal band corrections, taken from table 3 in IEC 61672-1.
_NOMINAL_A_WEIGHTING_CORRECTIONS = {
    10: -70.4, 12.5: -63.4, 16: -56.7, 20: -50.5, 25: -44.7, 31.5: -39.4,
    40: -34.6, 50: -30.2, 63: -26.2, 80: -22.5, 100: -19.1, 125: -16.1,
    160: -13.4, 200: -10.9, 250: -8.6, 315: -6.6, 400: -4.8, 500: -3.2,
    630: -1.9, 800: -0.8, 1000: 0.0, 1250: 0.6, 1600: 1.0, 2000: 1.2,
    2500: 1.3, 3150: 1.2, 4000: 1.0, 5000: 0.5, 6300: -0.1, 8000: -1.1,
    10000: -2.5, 12500: -4.3, 16000: -6.6, 20000: -9.3,
}
_NOMINAL_C_WEIGHTING_CORRECTIONS = {
    10: -14.3, 12.5: -11.2, 16: -8.5, 20: -6.2, 25: -4.4, 31.5: -3.0,
    40: -2.0, 50: -1.3, 63: -0.8, 80: -0.5, 100: -0.3, 125: -0.2,
    160: -0.1, 200: 0.0, 250: 0.0, 315: 0.0, 400: 0.0, 500: 0.0,
    630: 0.0, 800: 0.0, 1000: 0.0, 1250: 0.0, 1600: -0.1, 2000: -0.2,
    2500: -0.3, 3150: -0.5, 4000: -0.8, 5000: -1.3, 6300: -2.0, 8000: -3.0,
    10000: -4.4, 12500: -6.2, 16000: -8.5, 20000: -11.2,
}


def _calculate_A_weighted_level(f: Union[float, np.ndarray],
                                ) -> Union[float, np.ndarray]:
    """
    Calculates the level correction in dB of a frequency component
    when using the A weighting according to IEC 61672-1.
    """
    # formula (E.6) in the standard
    bracket_term = (_F_4**2 * f**4) / ((f**2 + _F_1**2) * (f**2 + _F_2**2)
                                      ** 0.5 * (f**2 + _F_3**2)**0.5
                                      * (f**2 + _F_4**2))
    return 10 * np.log10(bracket_term**2) - _A_1000


def _calculate_C_weighted_level(f: Union[float, np.ndarray],
                                ) -> Union[float, np.ndarray]:
    """
    Calculates the level correction in dB of a frequency component
    when using the C weighting according to IEC 61672-1.
    """
    # formula (E.1) in the standard
    bracket_term = (_F_4**2 * f**2) / ((f**2 + _F_1**2) * (f**2 + _F_4**2))
    return 10 * np.log10(bracket_term**2) - _C_1000


[docs] def frequency_weighting_curve(weighting: Literal["A", "C"], frequencies: list[float]): """ Calculates the level correction in dB of each frequency component when using the A or C frequency weighting. Parameters ---------- weighting: str ('A' or 'C') Which frequency weighting to use. frequencies: array-like The frequencies at which to evaluate the weighting curve. Returns ------- weights: numpy.ndarray[float] The weights in dB in the same shape as `frequencies`. These weights are in accordance with IEC 61672-1 [#]_. References ---------- .. [#] International Electrotechnical Commission, "IEC 61672-1:2013 - Electroacoustics - Sound level meters - Part 1: Specifications", IEC, 2013. Examples -------- Plot the weighting curves. .. plot:: >>> import pyfar as pf >>> import matplotlib.pyplot as plt >>> import numpy as np >>> plot_frequencies = np.logspace(1, 4.5, 100, base=10) >>> weights_A = pf.constants.frequency_weighting_curve( ... "A", plot_frequencies) >>> weights_C = pf.constants.frequency_weighting_curve( ... "C", plot_frequencies) >>> plt.plot(plot_frequencies, weights_A, label="A weighting") >>> plt.plot(plot_frequencies, weights_C, label="C weighting") >>> plt.semilogx() >>> plt.xlabel("f in Hz") >>> plt.ylabel("Weights in dB") >>> ticks = [10, 30, 100, 300, 1000, 3000, 10000, 30000] >>> plt.xticks(ticks, ticks) >>> plt.grid() >>> plt.legend() """ if weighting == "A": weights = _calculate_A_weighted_level(np.array(frequencies)) elif weighting == "C": weights = _calculate_C_weighted_level(np.array(frequencies)) else: raise ValueError("Allowed literals for weighting are 'A' and 'C'") return weights
[docs] def frequency_weighting_band_corrections( weighting: Literal["A", "C"], num_fractions: Literal[1, 3], frequency_range: tuple[float, float], ): """ Returns the A or C frequency weighting correction values for octave or third-octave bands in a given frequency range. This function uses the nominal frequencies and their respective weights as given in the IEC 62672-1 standard [#]_. Parameters ---------- weighting: str ('A' or 'C') Which frequency weighting type to use. num_fractions: {1, 3} The number of octave fractions. ``1`` returns octave band corrections, ``3`` returns third-octave band corrections. frequency_range: (float, float) A range of what nominal center frequencies to include. The lowest defined center frequency is 10 Hz and the highest is 20 kHz. Returns ------- nominal_frequencies: numpy.ndarray[float] The nominal center frequencies included in the given range. weights: numpy.ndarray[float] The correction values in dB for the specific frequency weighting. References ---------- .. [#] International Electrotechnical Commission, "IEC 61672-1:2013 - Electroacoustics - Sound level meters - Part 1: Specifications", IEC, 2013. Examples -------- Plot the band correction levels. .. plot:: >>> import pyfar as pf >>> import matplotlib.pyplot as plt >>> f_range = (10, 20000) >>> nominals_third, weights_A = pf.constants.frequency_weighting_band_corrections( ... "A", 3, f_range) >>> nominals_octave, weights_C = pf.constants.frequency_weighting_band_corrections( ... "C", 1, f_range) >>> # plotting >>> plt.plot(nominals_third, weights_A, "--", c=(0.5, 0.5, 0.5, 0.5)) >>> plt.plot(nominals_third, weights_A, "bo", label="A weighting in third bands") >>> plt.plot(nominals_octave, weights_C, "--", c=(0.5, 0.5, 0.5, 0.5)) >>> plt.plot(nominals_octave, weights_C, "go", label="C weighting in octave bands") >>> plt.legend() >>> ticks = nominals_octave[::2].astype(int) >>> plt.semilogx() >>> plt.xticks(ticks, ticks) >>> plt.xlabel("f in Hz") >>> plt.ylabel("Corrections in dB") >>> plt.grid() """ # noqa: E501 if weighting == "A": all_weights = _NOMINAL_A_WEIGHTING_CORRECTIONS elif weighting == "C": all_weights = _NOMINAL_C_WEIGHTING_CORRECTIONS else: raise ValueError("Allowed literals for weighting are 'A' and 'C'") nominals_in_range = pf.constants.fractional_octave_frequencies_nominal( num_fractions, frequency_range) weights_in_range = np.array([all_weights[n] for n in nominals_in_range]) return (nominals_in_range, weights_in_range)