Source code for mesh2hrtf.Output2HRTF.compute_dtfs
import numpy as np
import sofar as sf
import pyfar as pf
import spharpy
[docs]
def compute_dtfs(
sofa, smooth_fractions=None, phase="minimum", weights="equal"):
r"""
Compute directional transfer functions from HRIRs.
The directional transfer functions (DTFs) are computed by a spectral
division of the HRTFs by the diffuse field transfer function (DFTF)
.. math::
\mathrm{DTF} = \frac{\mathrm{HRTF}}{\mathrm{DFTF}}
where the DFTF is computed as the energetic average across source positions
and ears
.. math::
\mathrm{DFTF} = \sqrt{\sum_{n=0}^{N-1} |\mathrm{HRTF}|^2_{l,n}\,w_n +
\sum_{n=0}^{N-1} |\mathrm{HRTF}|^2_{r,n}\,w_n}
The index :math:`n` denotes the source position, :math:`|\cdot|` the
absolute spectrum, and :math:`\sum_n w_n=1` normalized area weights for
numerical integration (see below). The average across ears is made to not
alter binaural cues.
Parameters
----------
sofa : sofar Sofa object, str
Sofa object containing the sound pressure or filename of a SOFA file to
be loaded. SOFA object/file must be of the convention
SimpleFreeFieldHRIR or GeneralFIR
smooth_fractions : number, None, optional
Apply fractional octave smoothing to the DFTF using
:py:func:`pyfar.dsp.smooth_fractional_octave` to the DFTF.
E.g. a value of ``3`` applies third octave smoothing and a value of
``1`` applies octave smoothing. The default ``None`` does not apply any
smoothing.
phase : string, optional
Define the phase of the inverse DFTF.
``'minimum'``
generate a minimum phase response using
:py:func:`pyfar.dsp.minimum_phase`
``'linear'``
generate a linear phase response using
:py:func:`pyfar.dsp.linear_phase`
``'zero'``
generates a zero phase response.
The default is ``'minimum'``.
weights : optional
Define the weights used for the numerical integration
``'equal'``
Uses equal weights across source positions
``'voronoi'``
Computes spherical Voronoi weights using
:py:func:`spharpy.samplings.calculate_sampling_weights`. This
requires that all points defined by `sofa.SourcePosition` have
the same radius.
array like
Uses the weights provided in a list or numpy array. The size of the
array like must agree with the number of HRTFs
The default is ``'equal'``
Returns
-------
sofa : sofar Sofa.object
The DTFs as :py:class:`sofar.Sofa` object. Can be written to disk with
:py:func:`sofar.write_sofa`.
DTFT_inverse : pyfar Signal object
The inverse diffuse field transfer function as a
:py:class:`pyfar.Signal`.
"""
if isinstance(sofa, str):
sofa = sf.read_sofa(sofa)
else:
sofa = sofa.copy()
if sofa.GLOBAL_SOFAConventions not in [
"SimpleFreeFieldHRIR", "GeneralFIR"]:
raise ValueError(("Sofa object must have the conventions "
"SimpleFreeFieldHRIR or GeneralFIR"))
# get pyfar objects (use pf.io.convert_sofa once released)
hrir, coordinates, _ = pf.io.convert_sofa(sofa)
# get or check the weights
if weights == "equal":
weights = None
elif weights == "voronoi":
weights = spharpy.samplings.calculate_sampling_weights(
spharpy.SamplingSphere.from_coordinates(coordinates))
weights = weights[..., None]
elif isinstance(weights, (list, np.ndarray)):
weights = np.asarray(weights).flatten()
if weights.size != hrir.cshape[0]:
raise ValueError((
f"{weights.size} provided but {hrir.cshape[0]} expected "
"(number of HRIRs)"))
weights = weights[..., None]
else:
raise ValueError("weights must be 'equal', 'voronoi' or an array like")
# compute DFTF
dftf = pf.dsp.average(
hrir, "power", caxis=(0, 1), weights=weights)
if smooth_fractions is not None:
dftf, _ = pf.dsp.smooth_fractional_octave(dftf, smooth_fractions)
# get inverse DFTF
if phase == "minimum":
dftf_inv = pf.dsp.minimum_phase(1 / dftf, truncate=False)
elif phase == "linear":
dftf_inv = pf.dsp.linear_phase(1 / dftf, dftf.n_samples / 2)
elif phase == "zero":
dftf_inv = 1 / dftf
else:
raise ValueError(
f"phase is '{phase}' but must be 'minimum' or 'linear'")
# compute DTF
dtf = hrir * dftf_inv
sofa.Data_IR = dtf.time
return sofa, dftf_inv