def properties(signal, sample_rate): """Return a list of some wave properties for a given 1-D signal """ # Measurements that include DC component DC_offset = mean(signal) # Maximum/minimum sample value # Estimate of true bit rate # Remove DC component signal -= mean(signal) # Measurements that don't include DC signal_level = rms_flat(signal) peak_level = max(absolute(signal)) crest_factor = peak_level / signal_level # Apply the A-weighting filter to the signal weighted = A_weight(signal, sample_rate) weighted_level = rms_flat(weighted) # TODO: rjust instead of tabs return [ 'DC offset:\t%f (%.3f%%)' % (DC_offset, DC_offset * 100), 'Crest factor:\t%.3f (%.3f dB)' % (crest_factor, dB(crest_factor)), 'Peak level:\t%.3f (%.3f dBFS)' % (peak_level, dB(peak_level)), # Doesn't account for intersample peaks! 'RMS level:\t%.3f (%.3f dBFS)' % (signal_level, dB(signal_level)), 'RMS A-weighted:\t%.3f (%.3f dBFS(A), %.3f dB)' % (weighted_level, dB(weighted_level), dB( weighted_level / signal_level)), '-----------------', ]
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))