Source code for mesh2hrtf.Output2HRTF.compute_hrirs

import numpy as np
import sofar as sf
from .output2hrtf import _get_sofa_object


[docs] def compute_hrirs(sofa, n_shift, sampling_rate=None): """ Compute HRIR from HRTF by means of the inverse Fourier transform. This requires the following: 1. The HRTFs contained in `sofa` have been referenced using `reference_hrtfs()`. 2. HRTF must be available for frequencies f_0 to f_1 in constant steps. f_0 must be > 0 Hz and f_1 is assumed to be half the sampling rate. HRIRs are computed with the following steps 1. Add data for 0 Hz. The HRTF at 0 Hz is 1 (0 dB) by definition because the HRTF describes the filtering of incoming sound by the human anthropometry (which does not change the sound at 0 Hz). 2. Only the absolute value for the data at half the sampling rate is used. Otherwise the next step would produce complex output 3. The HRTF spectrum is mirrored and the HRIR is obtained through the inverse Fourier transform 4. The HRIRs are circularly shifted by `n_shift` samples to enforce a causal system. A good shift value for a sampling rate of 44.1 kHz might be between 20 and 40 samples. 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 SimpleFreeFieldHRTF or GeneralTF n_shift : int Amount the HRIRs are shifted to enforce a causal system. sampling_rate : int The sampling rate in Hz. The sampling rate can two times any frequency for which the HRTF was computed. The default ``None`` assumes the sampling rate to be two times the highest frequency for which the HRTF was computed. Returns ------- sofa : sofar Sofa.object HRIRs Notes ----- HRIRs for different sampling rates can be generated from a single SOFA file if discarding or adding some data. """ if isinstance(sofa, str): sofa = sf.read_sofa(sofa) else: sofa = sofa.copy() if sofa.GLOBAL_SOFAConventions not in ["SimpleFreeFieldHRTF", "GeneralTF"]: raise ValueError(("Sofa object must have the conventions " "SimpleFreeFieldHRTF or GeneralTF")) # check if the frequency vector has the correct format frequencies = sofa.N if any(np.abs(np.diff(frequencies, 2)) > .1) or frequencies[0] < .1: raise ValueError( ('The frequency vector must go from f_1 > 0 to' 'f_2 (half the sampling rate) in equidistant steps.')) # get the HRTF pressure = sofa.Data_Real + 1j * sofa.Data_Imag if sampling_rate is None: # detect the sampling rate sampling_rate = round(2*frequencies[-1]) else: # check if the specified sampling rate is valid idx = np.argmin(np.abs(2 * frequencies - sampling_rate)) error = np.abs(2 * frequencies[idx] - sampling_rate) if np.abs(error) > 1e-6: raise ValueError(( "The specified sampling rate is invalid. It must be two times " "any frequency for which the HRTF was computed.")) # discard frequencies above sampling_rate / 2 pressure = pressure[..., :idx + 1] frequencies = frequencies[:idx + 1] # add 0 Hz bin pressure = np.concatenate((np.ones((pressure.shape[0], pressure.shape[1], 1)), pressure), axis=2) # make sampling_rate/2 real pressure[:, :, -1] = np.abs(pressure[:, :, -1]) # ifft (take complex conjugate because sign conventions differ) hrir = np.fft.irfft(np.conj(pressure)) # shift to make causal # (path differences between the origin and the ear are usually # smaller than 30 cm but numerical HRIRs show stringer pre-ringing) hrir = np.roll(hrir, n_shift, axis=-1) sofa = _get_sofa_object( hrir, sofa.SourcePosition, sofa.SourcePosition_Type, sofa.ReceiverPosition, "HRIR", sofa.GLOBAL_ApplicationVersion, sampling_rate=sampling_rate) return sofa