def fft(x, axis=-1, padding_samples=0): """ Apply an FFT along the given dimension, and with the specified amount of zero-padding Args: x (ArrayWithUnits): an :class:`~zounds.core.ArrayWithUnits` instance which has one or more :class:`~zounds.timeseries.TimeDimension` axes axis (int): The axis along which the fft should be applied padding_samples (int): The number of padding zeros to apply along axis before performing the FFT """ if padding_samples > 0: padded = np.concatenate( [x, np.zeros((len(x), padding_samples), dtype=x.dtype)], axis=axis) else: padded = x transformed = np.fft.rfft(padded, axis=axis, norm='ortho') sr = audio_sample_rate(int(Seconds(1) / x.dimensions[axis].frequency)) scale = LinearScale.from_sample_rate(sr, transformed.shape[-1]) new_dimensions = list(x.dimensions) new_dimensions[axis] = FrequencyDimension(scale) return ArrayWithUnits(transformed, new_dimensions)
def morlet_filter_bank(samplerate, kernel_size, scale, scaling_factor, normalize=True): """ Create a bank of finite impulse response filters, with frequencies centered on the sub-bands of scale """ basis_size = len(scale) basis = np.zeros((basis_size, kernel_size), dtype=np.complex128) try: if len(scaling_factor) != len(scale): raise ValueError('scaling factor must have same length as scale') except TypeError: scaling_factor = np.repeat(float(scaling_factor), len(scale)) sr = int(samplerate) for i, band in enumerate(scale): scaling = scaling_factor[i] w = band.center_frequency / (scaling * 2 * sr / kernel_size) basis[i] = morlet(M=kernel_size, w=w, s=scaling) basis = basis.real if normalize: basis /= np.linalg.norm(basis, axis=-1, keepdims=True) + 1e-8 basis = ArrayWithUnits( basis, [FrequencyDimension(scale), TimeDimension(*samplerate)]) return basis
def test_can_apply_a_weighting_to_time_frequency_representation(self): td = TimeDimension(Seconds(1), Seconds(1)) fd = FrequencyDimension(LinearScale(FrequencyBand(20, 22050), 100)) tf = ArrayWithUnits(np.ones((90, 100)), [td, fd]) weighting = AWeighting() result = tf * weighting self.assertGreater(result[0, -1], result[0, 0])
def test_can_invert_frequency_weighting(self): td = TimeDimension(Seconds(1), Seconds(1)) fd = FrequencyDimension(LinearScale(FrequencyBand(20, 22050), 100)) tf = ArrayWithUnits(np.random.random_sample((90, 100)), [td, fd]) weighted = tf * AWeighting() inverted = weighted / AWeighting() np.testing.assert_allclose(tf, inverted)
def test_can_get_weights_from_tf_representation(self): td = TimeDimension(Seconds(1), Seconds(1)) fd = FrequencyDimension(LinearScale(FrequencyBand(20, 22050), 100)) tf = ArrayWithUnits(np.ones((90, 100)), [td, fd]) weighting = AWeighting() weights = weighting.weights(tf) self.assertEqual((100, ), weights.shape)
def _process(self, data): transformed = self._process_raw(data) sr = audio_sample_rate(data.dimensions[1].samples_per_second) scale = LinearScale.from_sample_rate(sr, transformed.shape[1]) yield ArrayWithUnits( transformed, [data.dimensions[0], FrequencyDimension(scale)])
def _process(self, data): raw = self._process_raw(data) sr = audio_sample_rate( int(data.shape[1] / data.dimensions[0].duration_in_seconds)) scale = LinearScale.from_sample_rate( sr, data.shape[1], always_even=self.scale_always_even) yield ArrayWithUnits( raw, [data.dimensions[0], FrequencyDimension(scale)])
def square(self, n_coeffs, do_overlap_add=False): """ Compute a "square" view of the frequency adaptive transform, by resampling each frequency band such that they all contain the same number of samples, and performing an overlap-add procedure in the case where the sample frequency and duration differ :param n_coeffs: The common size to which each frequency band should be resampled """ resampled_bands = [ self._resample(band, n_coeffs) for band in self.iter_bands() ] stacked = np.vstack(resampled_bands).T fdim = FrequencyDimension(self.scale) # TODO: This feels like it could be wrapped up nicely elsewhere chunk_frequency = Picoseconds( int( np.round(self.time_dimension.duration / Picoseconds(1) / n_coeffs))) td = TimeDimension(frequency=chunk_frequency) arr = ConstantRateTimeSeries( ArrayWithUnits(stacked.reshape(-1, n_coeffs, self.n_bands), dimensions=[self.time_dimension, td, fdim])) if not do_overlap_add: return arr # Begin the overlap add procedure overlap_ratio = self.time_dimension.overlap_ratio if overlap_ratio == 0: # no overlap add is necessary return ArrayWithUnits(stacked, [td, fdim]) step_size_samples = int(n_coeffs * overlap_ratio) first_dim = int( np.round((stacked.shape[0] * overlap_ratio) + (n_coeffs * overlap_ratio))) output = ArrayWithUnits(np.zeros((first_dim, self.n_bands)), dimensions=[td, fdim]) for i, chunk in enumerate(arr): start = step_size_samples * i stop = start + n_coeffs output[start:stop] += chunk.reshape((-1, self.n_bands)) return output
def _process(self, data): transformed = dct(data, norm='ortho', axis=self._axis) sr = audio_sample_rate( int(data.shape[1] / data.dimensions[0].duration_in_seconds)) scale = LinearScale.from_sample_rate( sr, transformed.shape[-1], always_even=self.scale_always_even) yield ArrayWithUnits( transformed, [data.dimensions[0], FrequencyDimension(scale)])
def fir_filter_bank(scale, taps, samplerate, window): basis = np.zeros((len(scale), taps)) basis = ArrayWithUnits( basis, [FrequencyDimension(scale), TimeDimension(*samplerate)]) nyq = samplerate.nyquist if window.ndim == 1: window = repeat(window, len(scale)) for i, band, win in zip(xrange(len(scale)), scale, window): start_hz = max(0, band.start_hz) stop_hz = min(nyq, band.stop_hz) freqs = np.linspace(start_hz / nyq, stop_hz / nyq, len(win)) freqs = [0] + list(freqs) + [1] gains = [0] + list(win) + [0] basis[i] = firwin2(taps, freqs, gains) return basis
def test_equal(self): fd1 = FrequencyDimension(LinearScale(FrequencyBand(20, 10000), 100)) fd2 = FrequencyDimension(LinearScale(FrequencyBand(20, 10000), 100)) self.assertEqual(fd1, fd2)
def test_not_equal(self): fd1 = FrequencyDimension(LinearScale(FrequencyBand(20, 10000), 100)) fd2 = FrequencyDimension(GeometricScale(20, 10000, 0.01, 100)) self.assertNotEqual(fd1, fd2)
def apply_scale(short_time_fft, scale, window=None): magnitudes = np.abs(short_time_fft.real) spectrogram = scale.apply(magnitudes, window) dimensions = short_time_fft.dimensions[:-1] + (FrequencyDimension(scale), ) return ArrayWithUnits(spectrogram, dimensions)
def _new_dim(self): return FrequencyDimension(self.scale)