def test_fft_orthogonality_noise_even_fftw(noise, fft_lib_pyfftw): signal_spec = fft.rfft( noise.time, noise.n_samples, noise.sampling_rate, noise.fft_norm) transformed_signal_time = fft.irfft( signal_spec, noise.n_samples, noise.sampling_rate, noise.fft_norm) npt.assert_allclose( transformed_signal_time, noise.time, rtol=1e-10, atol=10*np.finfo(float).eps)
def test_fft_orthogonality_sine_odd_fftw(sine_odd, fft_lib_pyfftw): signal_spec = fft.rfft( sine_odd.time, sine_odd.n_samples, sine_odd.sampling_rate, sine_odd.fft_norm) transformed_signal_time = fft.irfft( signal_spec, sine_odd.n_samples, sine_odd.sampling_rate, sine_odd.fft_norm) npt.assert_allclose( transformed_signal_time, sine_odd.time, rtol=1e-10, atol=10*np.finfo(float).eps)
def domain(self, new_domain): """Set the domain of the signal.""" if new_domain not in self._VALID_DOMAINS: raise ValueError("Incorrect domain, needs to be time/freq.") if not (self._domain == new_domain): # Only process if we change domain if new_domain == 'time': # If the new domain should be time, we had a saved spectrum # and need to do an inverse Fourier Transform self.time = fft.irfft(self._data, self.n_samples, self._sampling_rate, self._fft_norm) elif new_domain == 'freq': # If the new domain should be freq, we had sampled time data # and need to do a Fourier Transform self.freq = fft.rfft(self._data, self.n_samples, self._sampling_rate, self._fft_norm)
def noise(n_samples, spectrum="white", rms=1, sampling_rate=44100, seed=None): """ Generate single or multi channel normally distributed white or pink noise. The pink noise is generated by applying a sqrt(1/f) filter to the spectrum. Parameters ---------- n_samples : int The length of the signal in samples spectrum : str, optional 'white' to generate noise with constant energy across frequency. 'pink' to generate noise with constant energy across filters with constant relative bandwith. rms : double, array like, optional The route mean square (RMS) value of the noise signal. A multi channel noise signal is generated if an array of RMS values is passed. The default is 1. sampling_rate : int, optional The sampling rate in Hz. The default is 44100. seed : int, None The seed for the random generator. Pass a seed to obtain identical results for multiple calls of white noise. The default is None, which will yield different results with every call. Returns ------- signal : Signal The noise as a Signal object. The Signal is in the time domain and has the 'rms' FFT normalization (see pyfar.fft.normalization). """ # generate the noise rms = np.atleast_1d(rms) n_samples = int(n_samples) cshape = np.atleast_1d(rms).shape rng = np.random.default_rng(seed) noise = rng.standard_normal(np.prod(cshape + (n_samples, ))) noise = noise.reshape(cshape + (n_samples, )) if spectrum == "pink": # apply 1/f filter in the frequency domain noise = fft.rfft(noise, n_samples, sampling_rate, 'none') noise /= np.sqrt(np.arange(1, noise.shape[-1] + 1)) noise = fft.irfft(noise, n_samples, sampling_rate, 'none') elif spectrum != "white": raise ValueError( f"spectrum is '{spectrum}' but must be 'white' or 'pink'") # level the noise rms_current = np.atleast_1d(np.sqrt(np.mean(noise**2, axis=-1))) for idx in np.ndindex(rms.shape): noise[idx] = noise[idx] / rms_current[idx] * rms[idx] # save to Signal nl = "\n" # required as variable because f-strings cannot contain "\" comment = f"{spectrum} noise signal (rms = {str(rms).replace(nl, ',')})" signal = Signal(noise, sampling_rate, fft_norm="rms", comment=comment) return signal