def interaural_level_spectrum(self, azimuth, level_spectrum_filter=None): ''' Apply a frequency-dependend interaural level difference corresponding to a given azimuth to a binaural sound. The level difference cues are taken from a filter generated with the _make_level_spectrum_filter function from an hrtf recording. The default will generate the filter from the MIT KEMAR recordings. The left and right channel of the sound should have the same level. Example: >>> noise = Binaural.pinknoise(kind='diotic') >>> noise.interaural_level_spectrum(azimuth=-45).play() ''' if not level_spectrum_filter: ils = Binaural._make_level_spectrum_filter( ) # TODO: should cache this as a file in /data or global ils = ils[:, 1:] # remove the frequency values (not necessary here) azis = ils[0, :] # get vector of azimuths in ils filter bank ils = ils[1:, :] # the rest is the filter # interpolate levels at azimuth levels = [ numpy.interp(azimuth, azis, ils[:, i]) for i in range(ils.shape[1]) ] fbank = Filter.cos_filterbank(length=self.nsamples, samplerate=self.samplerate) subbands_left = fbank.apply(self.left) subbands_right = fbank.apply(self.right) # change subband levels: subbands_left.level = subbands_left.level + levels / 2 subbands_right.level = subbands_right.level - levels / 2 out_left = Filter.collapse_subbands(subbands_left, filter_bank=fbank) out_right = Filter.collapse_subbands(subbands_right, filter_bank=fbank) return Binaural([out_left, out_right])
def make_interaural_level_spectrum(hrtf=None): """ Compute the frequency band specific interaural intensity differences for all sound source azimuth's in a head-related transfer function. For every azimuth in the hrtf, the respective transfer function is applied to a sound. This sound is then divided into frequency sub-bands. The interaural level spectrum is the level difference between right and left for each of these sub-bands for each azimuth. When individual HRTFs are not avilable, the level spectrum of the KEMAR mannequin may be used (default). Note that the computation may take a few minutes. Save the level spectrum to avoid re-computation, for instance with pickle or numpy.save (see documentation on readthedocs for examples). Arguments: hrtf (None | slab.HRTF): The head-related transfer function used to compute the level spectrum. If None, use the recordings from the KEMAR mannequin. Returns: (dict): A dictionary with keys `samplerate`, `frequencies` [n], `azimuths` [m], and `level_diffs` [n x m], where `frequencies` lists the centres of sub-bands for which the level difference was computed, and `azimuths` lists the sound source azimuth's in the hrft. `level_diffs` is a matrix of the interaural level difference for each sub-band and azimuth. Examples:: ils = slab.Binaural.make_interaural_level_spectrum() # get the ils from the KEMAR recordings ils['samplerate'] # the sampling rate ils['frequencies'] # the sub-band frequencies ils['azimuths'] # the sound source azimuth's for which the level difference was calculated ils['level_diffs'][5, :] # the level difference for each azimuth in the 5th sub-band """ if not hrtf: hrtf = HRTF.kemar() # load KEMAR by default # get the filters for the frontal horizontal arc idx = numpy.where((hrtf.sources[:, 1] == 0) & ( (hrtf.sources[:, 0] <= 90) | (hrtf.sources[:, 0] >= 270)))[0] # at this point, we could just get the transfer function of each filter with hrtf.data[idx[i]].tf(), # but it may be better to get the spectral left/right differences with ERB-spaced frequency resolution: azi = hrtf.sources[idx, 0] # 270<azi<360 -> azi-360 to get negative angles on the left azi[azi >= 270] = azi[azi >= 270]-360 sort = numpy.argsort(azi) fbank = Filter.cos_filterbank(samplerate=hrtf.samplerate, pass_bands=True) freqs = fbank.filter_bank_center_freqs() noise = Sound.pinknoise(duration=5., samplerate=hrtf.samplerate) ils = dict() ils['samplerate'] = hrtf.samplerate ils['frequencies'] = freqs ils['azimuths'] = azi[sort] ils['level_diffs'] = numpy.zeros((len(freqs), len(idx))) for n, i in enumerate(idx[sort]): # put the level differences in order of increasing angle noise_filt = Binaural(hrtf.data[i].apply(noise)) noise_bank_left = fbank.apply(noise_filt.left) noise_bank_right = fbank.apply(noise_filt.right) ils['level_diffs'][:, n] = noise_bank_right.level - noise_bank_left.level return ils
def _make_level_spectrum_filter(hrtf=None): ''' Generate a level spectrum from the horizontal recordings in an HRTF file. The defaut Filter.cos_filterbank is used and the same filter bank has to be used when applying the level spectrum to a sound. ''' from slab import DATAPATH if not hrtf: try: ils = numpy.load(DATAPATH + 'KEMAR_interaural_level_spectrum.npy') return ils except FileNotFoundError: hrtf = HRTF( DATAPATH + 'mit_kemar_normal_pinna.sofa') # load the hrtf file save_standard = True # get the filters for the frontal horizontal arc idx = numpy.where((hrtf.sources[:, 1] == 0) & ( (hrtf.sources[:, 0] <= 90) | (hrtf.sources[:, 0] >= 270)))[0] # at this point, we could just get the transfer function of each filter with hrtf.data[idx[i]].tf(), # but it may be better to get the spectral left/right differences with ERB-spaced frequency resolution: azi = hrtf.sources[idx, 0] # 270<azi<360 -> azi-360 to get negative angles the left azi[azi >= 270] = azi[azi >= 270] - 360 sort = numpy.argsort(azi) fbank = Filter.cos_filterbank(samplerate=hrtf.samplerate) freqs = fbank.filter_bank_center_freqs() noise = Sound.pinknoise(samplerate=hrtf.samplerate) ils = numpy.zeros((len(freqs) + 1, len(idx) + 1)) ils[:, 0] = numpy.concatenate( ([0], freqs)) # first row are the frequencies for n, i in enumerate( idx[sort] ): # put the level differences in order of increasing angle noise_filt = Binaural(hrtf.data[i].apply(noise)) noise_bank_left = fbank.apply(noise_filt.left) noise_bank_right = fbank.apply(noise_filt.right) ils[1:, n + 1] = noise_bank_right.level - noise_bank_left.level ils[0, n + 1] = azi[sort[n]] # first entry is the angle if save_standard: numpy.save(DATAPATH + 'KEMAR_interaural_level_spectrum.npy', ils) return ils
def interaural_level_spectrum(self, azimuth, ils=None): """ Apply an interaural level spectrum, corresponding to a sound sources azimuth, to a binaural sound. The interaural level spectrum consists of frequency specific interaural level differences which are computed from a head related transfer function (see the `make_interaural_level_spectrum()` method). The binaural sound is divided into frequency sub-bands and the levels of each sub-band are set according to the respective level in the interaural level spectrum. Then, the sub-bands are summed up again into one binaural sound. Arguments: azimuth (int | float): azimuth for which the interaural level spectrum is calculated. ils (dict): interaural level spectrum to apply. If None, `make_interaural_level_spectrum()` is called. For repeated use, it is better to generate and keep the ils in a variable to avoid re-computing it. Returns: (slab.Binaural): A binaural sound with the interaural level spectrum corresponding to the given azimuth. Examples:: noise = slab.Binaural.pinknoise(kind='diotic') ils = slab.Binaural.make_interaural_level_spectrum() # using default KEMAR HRTF noise.interaural_level_spectrum(azimuth=-45, ils=ils).play() """ if ils is None: ils = Binaural.make_interaural_level_spectrum() ils_samplerate = ils['samplerate'] original_samplerate = self.samplerate azis = ils['azimuths'] level_diffs = ils['level_diffs'] levels = numpy.array([numpy.interp(azimuth, azis, level_diffs[i, :]) for i in range(level_diffs.shape[0])]) # resample the signal to the rate of the HRTF from which the filter was computed: resampled = self.resample(samplerate=ils_samplerate) fbank = Filter.cos_filterbank(length=resampled.n_samples, samplerate=ils_samplerate, pass_bands=True) subbands_left = fbank.apply(resampled.left) subbands_right = fbank.apply(resampled.right) # change subband levels: subbands_left.level = subbands_left.level + levels / 2 subbands_right.level = subbands_right.level - levels / 2 out_left = Filter.collapse_subbands(subbands_left, filter_bank=fbank) out_right = Filter.collapse_subbands(subbands_right, filter_bank=fbank) out = Binaural([out_left, out_right]) return out.resample(samplerate=original_samplerate)