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