Skip to content

Commit

Permalink
Merge pull request #589 from pyfar/harmonize-frequency-range-parameter
Browse files Browse the repository at this point in the history
Harmonize frequency range parameter
  • Loading branch information
f-brinkmann committed Apr 26, 2024
2 parents 13be29a + 7e61868 commit 88dcccd
Show file tree
Hide file tree
Showing 9 changed files with 271 additions and 56 deletions.
4 changes: 2 additions & 2 deletions docs/concepts/resources/fft_norms.py
Expand Up @@ -11,9 +11,9 @@
impulse = pf.signals.impulse(
n_samples, sampling_rate=sampling_rate)
filter = pf.dsp.filter.fractional_octave_bands(
impulse, num_fractions=1, freq_range=(500, 700))
impulse, num_fractions=1, frequency_range=(500, 700))
ir = pf.dsp.filter.fractional_octave_bands(
impulse, num_fractions=1, freq_range=(200, 400), order=3)*n_samples/2
impulse, num_fractions=1, frequency_range=(200, 400), order=3)*n_samples/2
sine = pf.signals.sine(1e3, n_samples, sampling_rate=sampling_rate)
noise = pf.signals.noise(
n_samples, rms=1/np.sqrt(2), sampling_rate=sampling_rate)
Expand Down
3 changes: 2 additions & 1 deletion docs/concepts/resources/filter_types.py
Expand Up @@ -118,7 +118,8 @@

# DIN Filterbank
axis = ax[0]
y = pf.dsp.filter.fractional_octave_bands(impulse, 1, freq_range=(60, 12e3))
y = pf.dsp.filter.fractional_octave_bands(impulse, 1,
frequency_range=(60, 12e3))
pf.plot.freq(y, ax=axis)
axis.set_title('Fractional octave bands (room acoustics)')
axis.set_xlim(20, 20e3)
Expand Down
50 changes: 50 additions & 0 deletions pyfar/_utils.py
@@ -0,0 +1,50 @@
from pyfar.classes.warnings import PyfarDeprecationWarning
import warnings


# Decorator function to rename parameters to be deprecated
def rename_arg(arg_map, warning_message):
"""
Function for deprecating or renaming arguments.
Intercepts input if a deprecated argument is passed, replaces it with
a new argument and raises a PyfarDeprecationWarning.
Parameters
-----------
arg_map : dictionary
Map with deprecated argument to intercept and new argument to
replace it with: ``{"deprecated_argument": "new_argument"}``
warning_message: string
Message to be thrown with deprecation warning.
Returns
---------
function : function
Modified function with replaced arguments.
Examples
---------
Following example shows how a deprecated argument can be replaced by a
new argument while throwing a deprecation warning:
>>> from pyfar._utils import rename_arg
>>>
>>> @rename_arg({"old_arg": "new_arg"}, "old-arg will be deprecated in"
>>> " version x.x.x in favor of new_arg")
>>> def function(arg1, arg2, new_arg):
>>> return arg1, arg2, new_arg
>>>
>>> function(1, 2, old_arg=3)
"""

def decorator(func):
def wrapper(*args, **kwargs):
new_kwargs = {}
for kwarg, value in kwargs.items():
if kwarg in arg_map:
warnings.warn((warning_message), PyfarDeprecationWarning)
new_kwargs[arg_map.get(kwarg, kwarg)] = value
return func(*args, **new_kwargs)
return wrapper
return decorator
50 changes: 35 additions & 15 deletions pyfar/dsp/dsp.py
Expand Up @@ -4,6 +4,7 @@
import pyfar
from pyfar.dsp import fft
from pyfar.classes.warnings import PyfarDeprecationWarning
from pyfar._utils import rename_arg
import warnings


Expand Down Expand Up @@ -717,8 +718,11 @@ def _time_window_symmetric_interval_four(interval, window):
return win, win_start, win_stop


@rename_arg({"freq_range": "frequency_range"},
"freq_range parameter will be deprecated in pyfar 0.8.0 in "
"favor of frequency_range")
def regularized_spectrum_inversion(
signal, freq_range,
signal, frequency_range,
regu_outside=1., regu_inside=10**(-200/20), regu_final=None,
normalized=True):
r"""Invert the spectrum of a signal applying frequency dependent
Expand Down Expand Up @@ -747,7 +751,7 @@ def regularized_spectrum_inversion(
----------
signal : Signal
The signals which spectra are to be inverted.
freq_range : tuple, array_like, double
frequency_range : tuple, array_like, double
The upper and lower frequency limits outside of which the
regularization factor is to be applied.
regu_outside : float, optional
Expand All @@ -763,6 +767,11 @@ def regularized_spectrum_inversion(
normalized : bool
Flag to indicate if the normalized spectrum (according to
`signal.fft_norm`) should be inverted. The default is ``True``.
freq_range: tuple, array_like, double
The upper and lower frequency limits outside of which the
regularization factor is to be applied.
``'freq_range'`` parameter will be deprecated in pyfar 0.8.0 in favor
of ``'frequency_range'``.
Returns
Expand Down Expand Up @@ -791,9 +800,9 @@ def regularized_spectrum_inversion(
else:
data = signal.freq_raw

freq_range = np.asarray(freq_range)
frequency_range = np.asarray(frequency_range)

if freq_range.size < 2:
if frequency_range.size < 2:
raise ValueError(
"The frequency range needs to specify lower and upper limits.")

Expand All @@ -802,14 +811,15 @@ def regularized_spectrum_inversion(
regu_outside = np.ones(signal.n_bins, dtype=np.double) * regu_outside

idx_xfade_lower = signal.find_nearest_frequency(
[freq_range[0]/np.sqrt(2), freq_range[0]])
[frequency_range[0]/np.sqrt(2), frequency_range[0]])

regu_final = _cross_fade(regu_outside, regu_inside, idx_xfade_lower)

if freq_range[1] < signal.sampling_rate/2:
if frequency_range[1] < signal.sampling_rate/2:
idx_xfade_upper = signal.find_nearest_frequency([
freq_range[1],
np.min([freq_range[1]*np.sqrt(2), signal.sampling_rate/2])])
frequency_range[1],
np.min([frequency_range[1]*np.sqrt(2),
signal.sampling_rate/2])])

regu_final = _cross_fade(regu_final, regu_outside, idx_xfade_upper)

Expand Down Expand Up @@ -1418,8 +1428,11 @@ def find_impulse_response_start(
return np.squeeze(start_sample)


def deconvolve(system_output, system_input, fft_length=None, freq_range=None,
**kwargs):
@rename_arg({"freq_range": "frequency_range"},
"freq_range parameter will be deprecated in pyfar 0.8.0 in "
"favor of frequency_range")
def deconvolve(system_output, system_input, fft_length=None,
frequency_range=None, **kwargs):
r"""Calculate transfer functions by spectral deconvolution of two signals.
The transfer function :math:`H(\omega)` is calculated by spectral
Expand Down Expand Up @@ -1453,7 +1466,7 @@ def deconvolve(system_output, system_input, fft_length=None, freq_range=None,
The system input signal (e.g., used to perform a measurement).
The system input signal is zero padded, if it is shorter than the
system output signal.
freq_range : tuple, array_like, double
frequency_range : tuple, array_like, double
The upper and lower frequency limits outside of which the
regularization factor is to be applied. The default ``None``
bypasses the regularization, which might cause numerical
Expand All @@ -1467,6 +1480,14 @@ def deconvolve(system_output, system_input, fft_length=None, freq_range=None,
kwargs : key value arguments
Key value arguments to control the inversion of :math:`H(\omega)` are
passed to to :py:func:`~pyfar.dsp.regularized_spectrum_inversion`.
freq_range: tuple, array_like, double
The upper and lower frequency limits outside of which the
regularization factor is to be applied. The default ``None``
bypasses the regularization, which might cause numerical
instabilities in case of band-limited `system_input`. Also see
:py:func:`~pyfar.dsp.regularized_spectrum_inversion`.
``'freq_range'`` parameter will be deprecated in pyfar 0.8.0 in favor
of ``'frequency_range'``.
Returns
Expand All @@ -1482,7 +1503,6 @@ def deconvolve(system_output, system_input, fft_length=None, freq_range=None,
sweeps. Directors cut." J. Audio Eng. Soc. 49(6):443-471,
(2001, June).
"""

# Check if system_output and system_input are both type Signal
if not isinstance(system_output, pyfar.Signal):
raise TypeError('system_output has to be of type pyfar.Signal')
Expand All @@ -1493,8 +1513,8 @@ def deconvolve(system_output, system_input, fft_length=None, freq_range=None,
if not system_output.sampling_rate == system_input.sampling_rate:
raise ValueError("The two signals have different sampling rates!")

if freq_range is None:
freq_range = (0, system_input.sampling_rate/2)
if frequency_range is None:
frequency_range = (0, system_input.sampling_rate/2)

# Set fft_length to the max n_samples of both signals,
# if it is not explicitly set to a value
Expand All @@ -1518,7 +1538,7 @@ def deconvolve(system_output, system_input, fft_length=None, freq_range=None,
# multiply system_output signal with regularized inversed system_input
# signal to get the system response
inverse_input = regularized_spectrum_inversion(
system_input, freq_range, **kwargs)
system_input, frequency_range, **kwargs)
system_response = system_output * inverse_input

# Check if the signals have any comments,
Expand Down
23 changes: 17 additions & 6 deletions pyfar/dsp/filter/fractional_octaves.py
Expand Up @@ -2,6 +2,7 @@
import numpy as np
import scipy.signal as spsignal
import pyfar as pf
from pyfar._utils import rename_arg


def fractional_octave_frequencies(
Expand Down Expand Up @@ -153,11 +154,14 @@ def _center_frequencies_fractional_octaves_iec(nominal, num_fractions):
return nominal, exact


@rename_arg({"freq_range": "frequency_range"},
"freq_range parameter will be deprecated in pyfar 0.8.0 in "
"favor of frequency_range")
def fractional_octave_bands(
signal,
num_fractions,
sampling_rate=None,
freq_range=(20.0, 20e3),
frequency_range=(20.0, 20e3),
order=14):
"""Create and/or apply an energy preserving fractional octave filter bank.
Expand Down Expand Up @@ -189,11 +193,17 @@ def fractional_octave_bands(
sampling_rate : None, int
The sampling rate in Hz. Only required if signal is ``None``. The
default is ``None``.
freq_range : array, tuple, optional
frequency_range : array, tuple, optional
The lower and upper frequency limits. The default is
``frequency_range=(20, 20e3)``.
order : int, optional
Order of the Butterworth filter. The default is ``14``.
freq_range: array, tuple, optional
The lower and upper frequency limits. The default is
``frequency_range=(20, 20e3)``.
``'freq_range'`` parameter will be deprecated in pyfar 0.8.0 in favor
of ``'frequency_range'``.
Returns
-------
Expand All @@ -215,7 +225,7 @@ def fractional_octave_bands(
>>> # generate the data
>>> x = pf.signals.impulse(2**17)
>>> y = pf.dsp.filter.fractional_octave_bands(
... x, 1, freq_range=(20, 8e3))
... x, 1, frequency_range=(20, 8e3))
>>> # frequency domain plot
>>> y_sum = pf.FrequencyData(
... np.sum(np.abs(y.freq)**2, 0), y.frequencies)
Expand All @@ -225,6 +235,7 @@ def fractional_octave_bands(
... "Filter bands and the sum of their squared magnitudes")
"""

# check input
if (signal is None and sampling_rate is None) \
or (signal is not None and sampling_rate is not None):
Expand All @@ -234,7 +245,7 @@ def fractional_octave_bands(

sos = _coefficients_fractional_octave_bands(
sampling_rate=fs, num_fractions=num_fractions,
freq_range=freq_range, order=order)
frequency_range=frequency_range, order=order)

filt = pf.FilterSOS(sos, fs)
filt.comment = (
Expand All @@ -253,7 +264,7 @@ def fractional_octave_bands(

def _coefficients_fractional_octave_bands(
sampling_rate, num_fractions,
freq_range=(20.0, 20e3), order=14):
frequency_range=(20.0, 20e3), order=14):
"""Calculate the second order section filter coefficients of a fractional
octave band filter bank.
Expand Down Expand Up @@ -283,7 +294,7 @@ def _coefficients_fractional_octave_bands(
"""

f_crit = fractional_octave_frequencies(
num_fractions, freq_range, return_cutoff=True)[2]
num_fractions, frequency_range, return_cutoff=True)[2]

freqs_upper = f_crit[1]
freqs_lower = f_crit[0]
Expand Down

0 comments on commit 88dcccd

Please sign in to comment.