def THDN(signal, fs, weight=None): """Measure the THD+N for a signal and print the results Prints the estimated fundamental frequency and the measured THD+N. This is calculated from the ratio of the entire signal before and after notch-filtering. This notch-filters by nulling out the frequency coefficients ±10% of the fundamental TODO: Make R vs F reference a parameter (currently is R) TODO: Or report all of the above in a dictionary? """ # Get rid of DC and window the signal signal = np.asarray(signal) + 0.0 # Float-like array # TODO: Do this in the frequency domain, and take any skirts with it? signal -= mean(signal) window = general_cosine(len(signal), flattops['HFT248D']) windowed = signal * window del signal # Zero pad to nearest power of two new_len = next_fast_len(len(windowed)) windowed = concatenate((windowed, zeros(new_len - len(windowed)))) # Measure the total signal before filtering but after windowing total_rms = rms_flat(windowed) # Find the peak of the frequency spectrum (fundamental frequency) f = rfft(windowed) i = argmax(abs(f)) true_i = parabolic(log(abs(f)), i)[0] frequency = fs * (true_i / len(windowed)) # Filter out fundamental by throwing away values ±10% lowermin = int(true_i * 0.9) uppermin = int(true_i * 1.1) f[lowermin: uppermin] = 0 # TODO: Zeroing FFT bins is bad # Transform noise back into the time domain and measure it noise = irfft(f) # TODO: RMS and A-weighting in frequency domain? Parseval? if weight is None: pass elif weight == 'A': # Apply A-weighting to residual noise (Not normally used for # distortion, but used to measure dynamic range with -60 dBFS signal, # for instance) noise = A_weight(noise, fs) # TODO: filtfilt? tail end of filter? else: raise ValueError('Weighting not understood') # TODO: Return a dict or list of frequency, THD+N? return rms_flat(noise) / total_rms
def THDN(signal, fs, weight=None): """Measure the THD+N for a signal and print the results Prints the estimated fundamental frequency and the measured THD+N. This is calculated from the ratio of the entire signal before and after notch-filtering. This notch-filters by nulling out the frequency coefficients ±10% of the fundamental TODO: Make R vs F reference a parameter (currently is R) TODO: Or report all of the above in a dictionary? """ # Get rid of DC and window the signal signal = np.asarray(signal) + 0.0 # Float-like array # TODO: Do this in the frequency domain, and take any skirts with it? signal -= mean(signal) window = general_cosine(len(signal), flattops['HFT248D']) windowed = signal * window del signal # Zero pad to nearest power of two new_len = next_fast_len(len(windowed)) windowed = concatenate((windowed, zeros(new_len - len(windowed)))) # Measure the total signal before filtering but after windowing total_rms = rms_flat(windowed) # Find the peak of the frequency spectrum (fundamental frequency) f = rfft(windowed) i = argmax(abs(f)) true_i = parabolic(log(abs(f)), i)[0] frequency = fs * (true_i / len(windowed)) # Filter out fundamental by throwing away values ±10% lowermin = int(true_i * 0.9) uppermin = int(true_i * 1.1) f[lowermin:uppermin] = 0 # TODO: Zeroing FFT bins is bad # Transform noise back into the time domain and measure it noise = irfft(f) # TODO: RMS and A-weighting in frequency domain? Parseval? if weight is None: pass elif weight == 'A': # Apply A-weighting to residual noise (Not normally used for # distortion, but used to measure dynamic range with -60 dBFS signal, # for instance) noise = A_weight(noise, fs) # TODO: filtfilt? tail end of filter? else: raise ValueError('Weighting not understood') # TODO: Return a dict or list of frequency, THD+N? return rms_flat(noise) / total_rms
def THD(signal, fs): """Measure the THD for a signal This function is not yet trustworthy. Returns the estimated fundamental frequency and the measured THD, calculated by finding peaks in the spectrum. TODO: Make weighting a parameter TODO: Make R vs F reference a parameter (F as default??) """ # Get rid of DC and window the signal signal = np.asarray(signal) + 0.0 # Float-like array # TODO: Do this in the frequency domain, and take any skirts with it? signal -= mean(signal) window = general_cosine(len(signal), flattops['HFT248D']) windowed = signal * window del signal # Find the peak of the frequency spectrum (fundamental frequency) f = rfft(windowed) i = argmax(abs(f)) true_i = parabolic(log(abs(f)), i)[0] print('Frequency: %f Hz' % (fs * (true_i / len(windowed)))) print('fundamental amplitude: %.3f' % abs(f[i])) harmonics_num = 15 # Find the values for the first 15 harmonics. Includes harmonic peaks # only, by definition # TODO: Should peak-find near each one, not just assume that fundamental # was perfectly estimated. # Instead of limited to 15, figure out how many fit based on f0 and # sampling rate and report this "4 harmonics" and list the strength of each for x in range(2, harmonics_num): print('%.3f' % abs(f[i * x]), end=' ') THD = sum([abs(f[i * x]) for x in range(2, harmonics_num)]) / abs(f[i]) print('\nTHD (up to %d harmonic): %f%%' % (harmonics_num, (THD * 100))) return
def THD(signal, fs): """Measure the THD for a signal This function is not yet trustworthy. Returns the estimated fundamental frequency and the measured THD, calculated by finding peaks in the spectrum. TODO: Make weighting a parameter TODO: Make R vs F reference a parameter (F as default??) """ # Get rid of DC and window the signal signal = np.asarray(signal) + 0.0 # Float-like array # TODO: Do this in the frequency domain, and take any skirts with it? signal -= mean(signal) window = general_cosine(len(signal), flattops['HFT248D']) windowed = signal * window del signal # Find the peak of the frequency spectrum (fundamental frequency) f = rfft(windowed) i = argmax(abs(f)) true_i = parabolic(log(abs(f)), i)[0] print('Frequency: %f Hz' % (fs * (true_i / len(windowed)))) print('fundamental amplitude: %.3f' % abs(f[i])) # Find the values for the first 15 harmonics. Includes harmonic peaks # only, by definition # TODO: Should peak-find near each one, not just assume that fundamental # was perfectly estimated. # Instead of limited to 15, figure out how many fit based on f0 and # sampling rate and report this "4 harmonics" and list the strength of each for x in range(2, 15): print('%.3f' % abs(f[i * x]), end=' ') THD = sum([abs(f[i*x]) for x in range(2, 15)]) / abs(f[i]) print('\nTHD: %f%%' % (THD * 100)) return
def test_basic(self): assert_allclose(windows.general_cosine(5, [0.5, 0.3, 0.2]), [0.4, 0.3, 1, 0.3, 0.4]) assert_allclose(windows.general_cosine(4, [0.5, 0.3, 0.2], sym=False), [0.4, 0.3, 1, 0.3])
sig_xmitt = np.sin(2 * pi * fc * tsig) sig_xmitt *= hann(tsig.size) # create an interpolator sig_ier = interp1d(tsig, sig_xmitt, kind=3, bounds_error=False, fill_value=0.) # create time series as a sum of all arrivals x_sig = np.zeros_like(taxis) x_sig += sig_ier(taxis - time_dir) / r_dir x_sig -= sig_ier(taxis - time_surf) / r_surf x_sig -= sig_ier(taxis - time_bottom) / r_bottom # this is really not necassary, but this is a good window nuttall4c = [0.3635819, 0.4891775, 0.1365995, 0.0106411] window = general_cosine(winwidth, nuttall4c, sym=False) num_overlap = int(np.ceil(winwidth * .656)) num_windows = int(np.floor((taxis.size - winwidth) / num_overlap)) win_position = np.arange(num_windows) * num_overlap sig_FT = [] for wp in win_position: sig_FT.append(np.fft.rfft(x_sig[wp: wp + winwidth] * window)) sig_FT = np.array(sig_FT) win_time = (win_position + winwidth / 2) / fs win_time += taxis[0] faxis = np.arange(winwidth // 2 + 1) / winwidth * fs fbin = int(np.argmin(np.abs(faxis - fc)))
# .. math:: z = \frac{2 \pi j}{N}, j = 0...N - 1 # Since this uses the convention of starting at the origin, to reproduce the # window, we need to convert every other coefficient to a positive number: HFT90D = [1, 1.942604, 1.340318, 0.440811, 0.043097] # The paper states that the highest sidelobe is at -90.2 dB. Reproduce # Figure 42 by plotting the window and its frequency response, and confirm # the sidelobe level in red: from scipy.signal.windows import general_cosine from scipy.fftpack import fft, fftshift import matplotlib.pyplot as plt window = general_cosine(1000, HFT90D, sym=False) plt.plot(window) plt.title("HFT90D window") plt.ylabel("Amplitude") plt.xlabel("Sample") plt.figure() A = fft(window, 10000) / (len(window)/2.0) freq = np.linspace(-0.5, 0.5, len(A)) response = 20 * np.log10(np.abs(fftshift(A / abs(A).max()))) plt.plot(freq, response) plt.axis([-50/1000, 50/1000, -140, 0]) plt.title("Frequency response of the HFT90D window") plt.ylabel("Normalized magnitude [dB]") plt.xlabel("Normalized frequency [cycles per sample]") plt.axhline(-90.2, color='red')