audio_filters.equal_loudness_filter

Attributes

data

Classes

EqualLoudnessFilter

An equal-loudness filter which compensates for the human ear's non-linear

Functions

_numerator(→ numpy.ndarray)

Least-squares estimate of the numerator polynomial of a transfer function

_polystab(→ numpy.ndarray)

Stabilize a polynomial by reflecting any roots that lie outside the unit

yulewalk(→ tuple[numpy.ndarray, numpy.ndarray])

Design a recursive (IIR) digital filter that approximates an arbitrary

Module Contents

class audio_filters.equal_loudness_filter.EqualLoudnessFilter(samplerate: int = 44100)

An equal-loudness filter which compensates for the human ear’s non-linear response to sound. This filter corrects this by cascading a Yule-Walker filter and a Butterworth filter.

Designed for use with samplerate of 44.1kHz and above. If you’re using a lower samplerate, use with caution.

Code based on the matlab implementation at https://bit.ly/3eqh2HU (url shortened for ruff)

Target curve: https://i.imgur.com/3g2VfaM.png Yulewalk response: https://i.imgur.com/J9LnJ4C.png Butterworth and overall response: https://i.imgur.com/3g2VfaM.png

Images and original matlab implementation by David Robinson, 2001

https://en.wikipedia.org/wiki/Equal-loudness_contour

>>> filt = EqualLoudnessFilter()
>>> isinstance(filt.yulewalk_filter, IIRFilter)
True
process(sample: float) float

Process a single sample through both filters

>>> filt = EqualLoudnessFilter()
>>> filt.process(0.0)
0.0
butterworth_filter
yulewalk_filter
audio_filters.equal_loudness_filter._numerator(impulse_response: numpy.ndarray, denominator: numpy.ndarray, numerator_order: int) numpy.ndarray

Least-squares estimate of the numerator polynomial of a transfer function given its impulse response and (already known) denominator polynomial.

>>> num = _numerator(np.array([1.0, 0.0, 0.0]), np.array([1.0, 0.0, 0.0]), 1)
>>> np.round(num, 6)
array([1., 0.])
audio_filters.equal_loudness_filter._polystab(poly: numpy.ndarray) numpy.ndarray

Stabilize a polynomial by reflecting any roots that lie outside the unit circle back inside it. This keeps the resulting IIR filter stable without changing its magnitude response.

https://en.wikipedia.org/wiki/Minimum_phase

>>> np.round(_polystab(np.array([1.0, 2.0, 1.0])), 6)
array([1., 2., 1.])
>>> np.round(_polystab(np.array([1.0, 2.0, 1.01])), 6)
array([1.      , 1.980198, 0.990099])
audio_filters.equal_loudness_filter.yulewalk(order: int, frequencies: numpy.ndarray, magnitudes: numpy.ndarray, npt: int = 512) tuple[numpy.ndarray, numpy.ndarray]

Design a recursive (IIR) digital filter that approximates an arbitrary frequency response using the modified Yule-Walker method. This is a dependency-free re-implementation of MATLAB/Octave’s yulewalk so that the equal-loudness filter below no longer relies on a third-party package.

https://en.wikipedia.org/wiki/Autoregressive_model#Yule%E2%80%93Walker_equations

Parameters:
  • order – order of the filter to design

  • frequencies – sample points on [0, 1] where 1 is the Nyquist frequency, in increasing order and starting at 0

  • magnitudes – desired (linear) magnitude at each point in frequencies

  • npt – number of points used to estimate the frequency response

Returns:

(a_coeffs, b_coeffs), the denominator and numerator polynomials

>>> a, b = yulewalk(4, np.array([0.0, 0.5, 1.0]), np.array([1.0, 0.5, 0.0]))
>>> len(a), len(b)
(5, 5)
>>> bool(np.all(np.abs(np.roots(a)) < 1))  # the designed filter is stable
True

Mismatched inputs and non-increasing frequencies are rejected:

>>> yulewalk(4, np.array([0.0, 1.0]), np.array([1.0]))
Traceback (most recent call last):
    ...
ValueError: frequencies and magnitudes must have the same length
>>> yulewalk(4, np.array([0.0, 1.0, 0.5]), np.array([1.0, 0.5, 0.0]))
Traceback (most recent call last):
    ...
ValueError: frequencies must be in increasing order
audio_filters.equal_loudness_filter.data