Source code for mesh2hrtf.Output2HRTF.compute_dtfs

import numpy as np
import sofar as sf
import pyfar as pf


[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 <https://pyfar.readthedocs.io/en/latest/modules/pyfar.dsp.html\ ?highlight=smooth#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 <https://pyfar.readthedocs.io/en/latest/modules/pyfar.dsp.html?\ highlight=minimum%20phase#pyfar.dsp.minimum_phase>`_ ``'linear'`` generate a `linear phase response <https://pyfar.readthedocs.io/en/latest/modules/pyfar.dsp.html?\ highlight=linear%20phase#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'`` Uses `spherical Voronoi weights <https://pyfar.readthedocs.io/en/latest/modules/pyfar.samplings.html\ ?highlight=voronoi#pyfar.samplings.calculate_sph_voronoi_weights>`_ 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 Sofa object. Can be written to disk with `sofar.write_sofa <https://sofar.readthedocs.io/en/latest/sofar.html#sofar.sofar.write_sofa\ sofar.sofar.write_sofa>`_. DTFT_inverse : pyfar Signal object The inverse diffuse field transfer function as a `pyfar signal object <https://pyfar.readthedocs.io/en/latest/classes/pyfar.audio.html#\ pyfar.classes.audio.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 = pf.samplings.calculate_sph_voronoi_weights(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