def freq_from_hps(signal, fs): """Estimate frequency using harmonic product spectrum Low frequency noise piles up and overwhelms the desired peaks """ N = len(signal) signal -= mean(signal) # Remove DC offset # Compute Fourier transform of windowed signal windowed = signal * kaiser(N, 100) # Get spectrum X = log(abs(rfft(windowed))) # Downsample sum logs of spectra instead of multiplying hps = copy(X) for h in arange(2, 9): # TODO: choose a smarter upper limit dec = decimate(X, h) hps[:len(dec)] += dec # Find the peak and interpolate to get a more accurate peak i_peak = argmax(hps[:len(dec)]) i_interp = parabolic(hps, i_peak)[0] # Convert to equivalent frequency return fs * i_interp / N # Hz
def freq_from_autocorr(signal, fs): """Estimate frequency using autocorrelation Pros: Best method for finding the true fundamental of any repeating wave, even with strong harmonics or completely missing fundamental Cons: Not as accurate, doesn't find fundamental for inharmonic things like musical instruments, this implementation has trouble with finding the true peak """ # Calculate autocorrelation (same thing as convolution, but with one input # reversed in time), and throw away the negative lags signal -= mean(signal) # Remove DC offset corr = fftconvolve(signal, signal[::-1], mode='full') corr = corr[len(corr)/2:] # Find the first low point d = diff(corr) start = find(d > 0)[0] # Find the next peak after the low point (other than 0 lag). This bit is # not reliable for long signals, due to the desired peak occurring between # samples, and other peaks appearing higher. i_peak = argmax(corr[start:]) + start i_interp = parabolic(corr, i_peak)[0] return fs / i_interp
def freq_from_autocorr(signal, fs): """Estimate frequency using autocorrelation Pros: Best method for finding the true fundamental of any repeating wave, even with strong harmonics or completely missing fundamental Cons: Not as accurate, doesn't work for inharmonic things like musical instruments, this implementation has trouble with finding the true peak """ # Calculate autocorrelation (same thing as convolution, but with one input # reversed in time), and throw away the negative lags signal -= mean(signal) # Remove DC offset corr = fftconvolve(signal, signal[::-1], mode='full') corr = corr[len(corr)/2:] # Find the first low point d = diff(corr) start = find(d > 0)[0] # Find the next peak after the low point (other than 0 lag). This bit is # not reliable for long signals, due to the desired peak occurring between # samples, and other peaks appearing higher. i_peak = argmax(corr[start:]) + start i_interp = parabolic(corr, i_peak)[0] return fs / i_interp
def freq_from_hps(signal, fs): """Estimate frequency using harmonic product spectrum Low frequency noise piles up and overwhelms the desired peaks """ N = len(signal) signal -= mean(signal) # Remove DC offset # Compute Fourier transform of windowed signal windowed = signal * kaiser(N, 100) # Get spectrum X = log(abs(rfft(windowed))) # Remove mean of spectrum (so sum is not increasingly offset # only in overlap region) X -= mean(X) # Downsample sum logs of spectra instead of multiplying hps = copy(X) for h in arange(2, 9): # TODO: choose a smarter upper limit h = int(h) # https://github.com/scipy/scipy/pull/7351 dec = decimate(X, h, zero_phase=True) hps[:len(dec)] += dec # Find the peak and interpolate to get a more accurate peak i_peak = argmax(hps[:len(dec)]) i_interp = parabolic(hps, i_peak)[0] # Convert to equivalent frequency return fs * i_interp / N # Hz
def THD(signal, sample_rate): """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. There are two definitions for THD, a power ratio or an amplitude ratio When finished, this will list both """ # Get rid of DC and window the signal signal -= mean(signal) # TODO: Do this in the frequency domain, and take any skirts with it? windowed = signal * kaiser(len(signal), 100) # 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' % (sample_rate * (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]), THD = sum([abs(f[i*x]) for x in range(2,15)]) / abs(f[i]) print '\nTHD: %f%%' % (THD * 100) return
def freq_from_hps(signal, fs): """Estimate frequency using harmonic product spectrum Low frequency noise piles up and overwhelms the desired peaks """ N = len(signal) signal -= mean(signal) # Remove DC offset # Compute Fourier transform of windowed signal windowed = signal * kaiser(N, 100) # Get spectrum X = log(abs(rfft(windowed, _next_regular(N)))) # Downsample sum logs of spectra instead of multiplying hps = copy(X) for h in arange(2, 9): # TODO: choose a smarter upper limit dec = decimate(X, h) hps[:len(dec)] += dec # Find the peak and interpolate to get a more accurate peak i_peak = argmax(hps[:len(dec)]) i_interp = parabolic(hps, i_peak)[0] # Convert to equivalent frequency return fs * i_interp / N # Hz
def freq_from_fft(sig, fs): # Compute Fourier transform of windowed signal windowed = sig * blackmanharris(len(sig)) fe = rfft(windowed) # Find the peak and interpolate to get a more accurate peak i = argmax(abs(fe)) # Just use this for less-accurate, naive version true_i = parabolic(log(abs(fe)), i)[0] # Convert to equivalent frequency return fs * true_i / len(windowed)
def THDN(signal, sample_rate): """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 """ # Get rid of DC and window the signal signal -= mean( signal ) # TODO: Do this in the frequency domain, and take any skirts with it? windowed = signal * kaiser(len(signal), 100) del signal # Zero pad to nearest power of two new_len = 2**ceil(log(len(windowed)) / log(2)) 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] print 'Frequency: %f Hz' % (sample_rate * (true_i / len(windowed))) # Filter out fundamental by throwing away values ±10% lowermin = true_i - 0.1 * true_i uppermin = true_i + 0.1 * true_i f[lowermin:uppermin] = 0 # Transform noise back into the time domain and measure it noise = irfft(f) THDN = rms_flat(noise) / total_rms # TODO: RMS and A-weighting in frequency domain? # Apply A-weighting to residual noise (Not normally used for distortion, # but used to measure dynamic range with -60 dBFS signal, for instance) weighted = A_weight(noise, sample_rate) THDNA = rms_flat(weighted) / total_rms print "THD+N: %.4f%% or %.1f dB" % (THDN * 100, 20 * log10(THDN)) print "A-weighted: %.4f%% or %.1f dB(A)" % (THDNA * 100, 20 * log10(THDNA))
def THDN(signal, sample_rate): """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 """ # Get rid of DC and window the signal signal -= mean(signal) # TODO: Do this in the frequency domain, and take any skirts with it? windowed = signal * kaiser(len(signal), 100) del signal # Zero pad to nearest power of two new_len = 2**ceil( log(len(windowed)) / log(2) ) 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] print 'Frequency: %f Hz' % (sample_rate * (true_i / len(windowed))) # Filter out fundamental by throwing away values ±10% lowermin = true_i - 0.1 * true_i uppermin = true_i + 0.1 * true_i f[lowermin: uppermin] = 0 # Transform noise back into the time domain and measure it noise = irfft(f) THDN = rms_flat(noise) / total_rms # TODO: RMS and A-weighting in frequency domain? # Apply A-weighting to residual noise (Not normally used for distortion, # but used to measure dynamic range with -60 dBFS signal, for instance) weighted = A_weight(noise, sample_rate) THDNA = rms_flat(weighted) / total_rms print "THD+N: %.4f%% or %.1f dB" % (THDN * 100, 20 * log10(THDN)) print "A-weighted: %.4f%% or %.1f dB(A)" % (THDNA * 100, 20 * log10(THDNA))
def freq_from_fft(signal, fs): """Estimate frequency from peak of FFT Pros: Accurate, usually even more so than zero crossing counter (1000.000004 Hz for 1000 Hz, for instance). Due to parabolic interpolation being a very good fit for windowed log FFT peaks? https://ccrma.stanford.edu/~jos/sasp/Quadratic_Interpolation_Spectral_Peaks.html Accuracy also increases with signal length Cons: Doesn't find the right value if harmonics are stronger than fundamental, which is common. """ N = len(signal) # Compute Fourier transform of windowed signal windowed = signal * kaiser(N, 100) f = rfft(windowed) # Find the peak and interpolate to get a more accurate peak i_peak = argmax(abs(f)) # Just use this value for less-accurate result i_interp = parabolic(log(abs(f)), i_peak)[0] # Convert to equivalent frequency return fs * i_interp / N # Hz