Last active
October 18, 2024 13:16
Revisions
-
endolith revised this gist
Jun 28, 2019 . 1 changed file with 18 additions and 12 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -46,7 +46,9 @@ def THDN(signal, sample_rate): if not better. """ # Get rid of DC and window the signal # TODO: Do this in the frequency domain, and take any skirts with it? signal -= mean(signal) windowed = signal * blackmanharris(len(signal)) # TODO Kaiser? # Measure the total signal before filtering but after windowing @@ -57,15 +59,18 @@ def THDN(signal, sample_rate): # minima f = rfft(windowed) i = argmax(abs(f)) # Not exact print('Frequency: %f Hz' % (sample_rate * (i / len(windowed)))) lowermin, uppermin = find_range(abs(f), i) f[lowermin: uppermin] = 0 # Transform noise back into the signal domain and measure it # TODO: Could probably calculate the RMS directly in the frequency domain # instead noise = irfft(f) THDN = rms_flat(noise) / total_rms print("THD+N: %.4f%% or %.1f dB" % (THDN * 100, 20 * log10(THDN))) def load(filename): @@ -93,35 +98,36 @@ def analyze_channels(filename, function): file """ signal, sample_rate, channels = load(filename) print('Analyzing "' + filename + '"...') if channels == 1: # Monaural function(signal, sample_rate) elif channels == 2: # Stereo if np.array_equal(signal[:, 0], signal[:, 1]): print('-- Left and Right channels are identical --') function(signal[:, 0], sample_rate) else: print('-- Left channel --') function(signal[:, 0], sample_rate) print('-- Right channel --') function(signal[:, 1], sample_rate) else: # Multi-channel for ch_no, channel in enumerate(signal.transpose()): print('-- Channel %d --' % (ch_no + 1)) function(channel, sample_rate) files = sys.argv[1:] if files: for filename in files: try: analyze_channels(filename, THDN) except Exception as e: print('Couldn\'t analyze "' + filename + '"') print(e) print() else: sys.exit("You must provide at least one file to analyze") -
endolith revised this gist
Jun 26, 2016 . 1 changed file with 48 additions and 30 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,20 +1,26 @@ from __future__ import division import sys from scipy.signal import blackmanharris from numpy.fft import rfft, irfft from numpy import argmax, sqrt, mean, absolute, arange, log10 import numpy as np try: import soundfile as sf except ImportError: from scikits.audiolab import Sndfile def rms_flat(a): """ Return the root mean square of all the elements of *a*, flattened out. """ return sqrt(mean(absolute(a)**2)) def find_range(f, x): """ Find range between nearest local minima from peak at index x """ for i in arange(x+1, len(f)): if f[i+1] >= f[i]: @@ -26,17 +32,18 @@ def find_range(f, x): break return (lowermin, uppermin) 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. Currently this tries to find the "skirt" around the fundamental and notch out the entire thing. A fixed-width filter would probably be just as good, if not better. """ # Get rid of DC and window the signal signal -= mean(signal) # TODO: Do this in the frequency domain, and take any skirts with it? @@ -45,11 +52,12 @@ def THDN(signal, sample_rate): # Measure the total signal before filtering but after windowing total_rms = rms_flat(windowed) # Find the peak of the frequency spectrum (fundamental frequency), and # filter the signal by throwing away values between the nearest local # minima f = rfft(windowed) i = argmax(abs(f)) print 'Frequency: %f Hz' % (sample_rate * (i / len(windowed))) # Not exact lowermin, uppermin = find_range(abs(f), i) f[lowermin: uppermin] = 0 @@ -59,22 +67,31 @@ def THDN(signal, sample_rate): THDN = rms_flat(noise) / total_rms print "THD+N: %.4f%% or %.1f dB" % (THDN * 100, 20 * log10(THDN)) def load(filename): """ Load a wave file and return the signal, sample rate and number of channels. Can be any format that libsndfile supports, like .wav, .flac, etc. """ try: wave_file = sf.SoundFile(filename) signal = wave_file.read() except ImportError: wave_file = Sndfile(filename, 'r') signal = wave_file.read_frames(wave_file.nframes) channels = wave_file.channels sample_rate = wave_file.samplerate return signal, sample_rate, channels def analyze_channels(filename, function): """ Given a filename, run the given analyzer function on each channel of the file """ signal, sample_rate, channels = load(filename) print 'Analyzing "' + filename + '"...' @@ -83,14 +100,14 @@ def analyze_channels(filename, function): function(signal, sample_rate) elif channels == 2: # Stereo if np.array_equal(signal[:, 0], signal[:, 1]): print '-- Left and Right channels are identical --' function(signal[:, 0], sample_rate) else: print '-- Left channel --' function(signal[:, 0], sample_rate) print '-- Right channel --' function(signal[:, 1], sample_rate) else: # Multi-channel for ch_no, channel in enumerate(signal.transpose()): @@ -102,8 +119,9 @@ def analyze_channels(filename, function): for filename in files: try: analyze_channels(filename, THDN) except Exception as e: print 'Couldn\'t analyze "' + filename + '"' print e print '' else: sys.exit("You must provide at least one file to analyze") -
endolith revised this gist
May 30, 2014 . 1 changed file with 5 additions and 3 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -26,11 +26,13 @@ at 96 kHz, showing the 16-bit quantization distortion: (Is this right? Theoretical SNR of a full-scale sine is 1.761+6.02⋅16 = -98.09 dB. Close, at least.) * THD<sub>F</sub> is the fundamental alone vs the harmonics alone * THD<sub>R</sub> is the total distorted signal vs the harmonics alone * THD+N is usually measured like THD<sub>R</sub>: the entire signal (not just the fundamental) vs the entire signal with the fundamental notched out (including noise and other components, not just the harmonics). (With low distortion figures, the difference between the entire signal and the fundamental is negligible.) This script is THD+N<sub>R</sub> (if that's how you write that). The primary problem with the current script is that I don't know how much of the surrounding region of the peak to throw away. Probably should be related -
endolith revised this gist
May 30, 2014 . 1 changed file with 16 additions and 11 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,10 +1,15 @@ Unfortunately, there are 2 versions of this. The other is here: https://github.com/endolith/waveform-analyzer I intend to either completely combine them or completely separate them, eventually. Somewhat crude THD+N calculator in Python Measures the total harmonic distortion plus noise (THD+N) for a given input signal, by guessing the fundamental frequency (finding the peak in the FFT), and notching it out in the frequency domain. This is a THD<sub>R</sub> measurement, meaning the denominator is the total distorted signal, not a bandpassed fundamental. Depends on Audiolab and SciPy @@ -20,17 +25,17 @@ at 96 kHz, showing the 16-bit quantization distortion: (Is this right? Theoretical SNR of a full-scale sine is 1.761+6.02⋅16 = -98.09 dB. Close, at least.) * THD<sub>F</sub> is the fundamental alone vs the harmonics alone * THD<sub>R</sub> is the total distorted signal vs the harmonics alone (this script) * THD+N is usually measured like THD<sub>R</sub>: the entire signal (not just the fundamental) vs the entire signal with the fundamental notched out. (With low distortion figures, the difference between the entire signal and the fundamental is negligible.) The primary problem with the current script is that I don't know how much of the surrounding region of the peak to throw away. Probably should be related to the mainlobe width of the windowing function, rather than what it's currently doing. To match other test equipment would just use a fixed bandwidth, though: width = 50 f[i-width: i+width+1] = 0 -
endolith revised this gist
Oct 25, 2012 . 1 changed file with 2 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,3 +1,5 @@ Updated version here: https://github.com/endolith/waveform-analyzer Somewhat crude THD+N calculator in Python Measures the total harmonic distortion plus noise (THD+N) for a given input -
endolith revised this gist
Mar 9, 2012 . 1 changed file with 3 additions and 2 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -16,8 +16,7 @@ at 96 kHz, showing the 16-bit quantization distortion: Frequency: 997.000000 Hz THD+N: 0.0016% or -96.1 dB (Is this right? Theoretical SNR of a full-scale sine is 1.761+6.02⋅16 = -98.09 dB. Close, at least.) According to the never-wrong Wikipedia: @@ -46,11 +45,13 @@ Also it computes the FFT for the entire sample, which is a waste of time. Use short samples. Adobe Audition with dither: 997 Hz 8-bit -49.8 997 Hz 16-bit -93.4 997 Hz 32-bit -143.9 Art Ludwig's Sound Files (http://members.cox.net/artludwig/): File Claimed Measured (dB) Reference 0.0% 0.0022% -93.3 Single-ended triode 5.0% 5.06% -25.9 -
endolith renamed this gist
Mar 9, 2012 . 1 changed file with 31 additions and 29 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -5,20 +5,22 @@ signal, by guessing the fundamental frequency (finding the peak in the FFT), and notching it out in the frequency domain. Depends on Audiolab and SciPy * http://www.ar.media.kyoto-u.ac.jp/members/david/softwares/audiolab/ * http://www.scipy.org/ Example of usage, with 997 Hz full-scale sine wave generated by Adobe Audition at 96 kHz, showing the 16-bit quantization distortion: > python thdcalculator.py "perfect 997 Hz no dither.flac" Frequency: 997.000000 Hz THD+N: 0.0016% or -96.1 dB (Is this right? Theoretical SNR of a FS sine is 1.761+6.02*16 = -98.09 dB. Close, at least.) According to the never-wrong Wikipedia: * THD is the fundamental alone vs the harmonics alone * THD+N is the entire signal (not just the fundamental) vs the entire signal with the fundamental notched out. (For low distortion, the difference between @@ -29,8 +31,8 @@ the surrounding region of the peak to throw away. Probably the way to match other test equipment is to just calculate the width of a certain bandwidth, but is that really ideal? width = 50 f[i-width: i+width+1] = 0 Instead of a fixed width, it currently just tries to find the nearest local minima and throw away everything between them. It works for almost all cases, @@ -44,35 +46,35 @@ Also it computes the FFT for the entire sample, which is a waste of time. Use short samples. Adobe Audition with dither: 997 Hz 8-bit -49.8 997 Hz 16-bit -93.4 997 Hz 32-bit -143.9 Art Ludwig's Sound Files (http://members.cox.net/artludwig/): File Claimed Measured (dB) Reference 0.0% 0.0022% -93.3 Single-ended triode 5.0% 5.06% -25.9 Solid state 0.5% 0.51% -45.8 Comparing a test device on an Audio Precision System One 22 kHz filtered vs recorded into my 96 kHz 24-bit sound card and measured with this script: Frequency AP THD+N Script THD+N 40 1.00% 1.04% 100 0.15% 0.19% 100 0.15% 0.14% 140 0.15% 0.17% 440 0.056% 0.057% 961 0.062% 0.067% 1021 0.080% 0.082% 1440 0.042% 0.041% 1483 0.15% 0.15% 4440 0.048% 0.046% 9974 7.1% 7.8% 10036 0.051% 0.068% 10723 8.2% 9.3% 13640 12.2% 16.8% 19998 20.2% 56.3% (nasty intermodulation distortion) 20044 0.22% 0.30% So it's mostly accurate. Mostly. -
endolith revised this gist
Mar 12, 2010 . 1 changed file with 26 additions and 9 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -27,6 +27,17 @@ def find_range(f, x): return (lowermin, uppermin) 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. Currently this tries to find the "skirt" around the fundamental and notch out the entire thing. A fixed-width filter would probably be just as good, if not better. """ # 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 * blackmanharris(len(signal)) # TODO Kaiser? @@ -49,44 +60,50 @@ def THDN(signal, sample_rate): print "THD+N: %.4f%% or %.1f dB" % (THDN * 100, 20 * log10(THDN)) def load(filename): """Load a wave file and return the signal, sample rate and number of channels. Can be any format that libsndfile supports, like .wav, .flac, etc. """ wave_file = Sndfile(filename, 'r') signal = wave_file.read_frames(wave_file.nframes) channels = wave_file.channels sample_rate = wave_file.samplerate return signal, sample_rate, channels def analyze_channels(filename, function): """Given a filename, run the given analyzer function on each channel of the file """ signal, sample_rate, channels = load(filename) print 'Analyzing "' + filename + '"...' if channels == 1: # Monaural function(signal, sample_rate) elif channels == 2: # Stereo if np.array_equal(signal[:,0],signal[:,1]): print '-- Left and Right channels are identical --' function(signal[:,0], sample_rate) else: print '-- Left channel --' function(signal[:,0], sample_rate) print '-- Right channel --' function(signal[:,1], sample_rate) else: # Multi-channel for ch_no, channel in enumerate(signal.transpose()): print '-- Channel %d --' % (ch_no + 1) function(channel, sample_rate) files = sys.argv[1:] if files: for filename in files: try: analyze_channels(filename, THDN) except: print 'Couldn\'t analyze "' + filename + '"' print '' else: sys.exit("You must provide at least one file to analyze") -
endolith revised this gist
Mar 11, 2010 . 1 changed file with 6 additions and 6 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -28,7 +28,7 @@ def find_range(f, x): def THDN(signal, sample_rate): # 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 * blackmanharris(len(signal)) # TODO Kaiser? # Measure the total signal before filtering but after windowing @@ -78,15 +78,15 @@ def analyze_file(filename): for ch_no, channel in enumerate(signal.transpose()): print '-- Channel %d --' % (ch_no + 1) THDN(channel, sample_rate) files = sys.argv[1:] if files: for filename in files: try: analyze_file(filename) except: print 'Couldn\'t analyze "' + filename + '"' print '' else: sys.exit("You must provide at least one file to analyze") -
endolith revised this gist
Mar 11, 2010 . 1 changed file with 30 additions and 19 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -55,27 +55,38 @@ def load(filename): sample_rate = wave_file.samplerate return signal, sample_rate, channels def analyze_file(filename): signal, sample_rate, channels = load(filename) print 'Analyzing "' + filename + '"...' if channels == 1: # Monaural THDN(signal, sample_rate) elif channels == 2: # Stereo if np.array_equal(signal[:,0],signal[:,1]): print '-- Left and Right channels are identical --' THDN(signal[:,0], sample_rate) else: print '-- Left channel --' THDN(signal[:,0], sample_rate) print '-- Right channel --' THDN(signal[:,1], sample_rate) else: # Multi-channel for ch_no, channel in enumerate(signal.transpose()): print '-- Channel %d --' % (ch_no + 1) THDN(channel, sample_rate) print '\n' files = sys.argv[1:] if files: for filename in files: #try: analyze_file(filename) #print 'No URL found in file', filename else: sys.exit("You must provide at least one file to analyze") -
endolith revised this gist
Mar 11, 2010 . 1 changed file with 27 additions and 5 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -4,6 +4,7 @@ from scipy.signal import blackmanharris from numpy.fft import rfft, irfft from numpy import argmax, sqrt, mean, absolute, arange, log10 import numpy as np def rms_flat(a): """Return the root mean square of all the elements of *a*, flattened out. @@ -42,18 +43,39 @@ def THDN(signal, sample_rate): f[lowermin: uppermin] = 0 # Transform noise back into the signal domain and measure it # TODO: Could probably calculate the RMS directly in the frequency domain instead noise = irfft(f) THDN = rms_flat(noise) / total_rms print "THD+N: %.4f%% or %.1f dB" % (THDN * 100, 20 * log10(THDN)) def load(filename): wave_file = Sndfile(filename, 'r') signal = wave_file.read_frames(wave_file.nframes) channels = wave_file.channels sample_rate = wave_file.samplerate return signal, sample_rate, channels filename = sys.argv[1] signal, sample_rate, channels = load(filename) print 'Analyzing "' + filename + '"...' if channels == 1: # Monaural THDN(signal, sample_rate) elif channels == 2: # Stereo if np.array_equal(signal[:,0],signal[:,1]): print '--Left and Right channels are identical:--' THDN(signal[:,0], sample_rate) else: print '--Left channel:--' THDN(signal[:,0], sample_rate) print '--Right channel:--' THDN(signal[:,1], sample_rate) else: # Multi-channel for ch_no, channel in enumerate(signal.transpose()): print '--Channel %d:--' % (ch_no + 1) THDN(channel, sample_rate) -
endolith revised this gist
Mar 11, 2010 . 1 changed file with 11 additions and 11 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -25,16 +25,10 @@ def find_range(f, x): break return (lowermin, uppermin) def THDN(signal, sample_rate): # 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 * blackmanharris(len(signal)) # TODO Kaiser? # Measure the total signal before filtering but after windowing total_rms = rms_flat(windowed) @@ -43,7 +37,7 @@ def THDN(signal, fs): # the signal by throwing away values between the nearest local minima f = rfft(windowed) i = argmax(abs(f)) print 'Frequency: %f Hz' % (sample_rate * (i / len(windowed))) # Not exact lowermin, uppermin = find_range(abs(f), i) f[lowermin: uppermin] = 0 @@ -52,8 +46,14 @@ def THDN(signal, fs): noise = irfft(f) THDN = rms_flat(noise) / total_rms print "THD+N: %.4f%% or %.1f dB" % (THDN * 100, 20 * log10(THDN)) def load(filename): wave_file = Sndfile(filename, 'r') signal = wave_file.read_frames(wave_file.nframes)[:,1] # TODO: Handle each channel separately sample_rate = wave_file.samplerate return signal, sample_rate filename = sys.argv[1] signal, sample_rate = load(filename) THDN(signal, sample_rate) -
endolith revised this gist
Mar 11, 2010 . 1 changed file with 31 additions and 24 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -25,28 +25,35 @@ def find_range(f, x): break return (lowermin, uppermin) def load(filename): wave_file = Sndfile(filename, 'r') signal = wave_file.read_frames(wave_file.nframes)[:,1] # TODO: Handle each channel separately fs = wave_file.samplerate return signal, fs def THDN(signal, fs): # 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 * blackmanharris(len(signal)) # Measure the total signal before filtering but after windowing total_rms = rms_flat(windowed) # Find the peak of the frequency spectrum (fundamental frequency), and filter # the signal by throwing away values between the nearest local minima f = rfft(windowed) i = argmax(abs(f)) print 'Frequency: %f Hz' % (fs * (i / len(windowed))) # Not exact lowermin, uppermin = find_range(abs(f), i) f[lowermin: uppermin] = 0 # Transform noise back into the signal domain and measure it # Could probably calculate the RMS directly in the frequency domain instead noise = irfft(f) THDN = rms_flat(noise) / total_rms print "THD+N: %.4f%% or %.1f dB" % (THDN * 100, 20 * log10(THDN)) filename = sys.argv[1] signal, fs = load(filename) THDN(signal, fs) -
endolith revised this gist
Mar 11, 2010 . 2 changed files with 6 additions and 4 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -54,8 +54,8 @@ Reference 0.0% 0.0022% -93.3 Single-ended triode 5.0% 5.06% -25.9 Solid state 0.5% 0.51% -45.8 Comparing a test device on an Audio Precision System One 22 kHz filtered vs recorded into my 96 kHz 24-bit sound card and measured with this script: Frequency AP THD+N Script THD+N 40 1.00% 1.04% This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,6 +1,6 @@ from __future__ import division import sys from scikits.audiolab import Sndfile from scipy.signal import blackmanharris from numpy.fft import rfft, irfft from numpy import argmax, sqrt, mean, absolute, arange, log10 @@ -26,7 +26,9 @@ def find_range(f, x): return (lowermin, uppermin) filename = sys.argv[1] wave_file = Sndfile(filename, 'r') signal = wave_file.read_frames(wave_file.nframes)[:,1] # TODO: Handle each channel separately fs = wave_file.samplerate # Get rid of DC and window the signal signal -= mean(signal) # TODO: Do this in the frequency domain, and take any skirts with it -
endolith revised this gist
Mar 11, 2010 . 1 changed file with 3 additions and 2 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -40,7 +40,8 @@ be considered part of the peak or part of the noise (jitter)? By comparison, Audio Precision manual states "Bandreject Response typically –3 dB at 0.725 f0 & 1.38 f0", which is about 0.93 octaves. Also it computes the FFT for the entire sample, which is a waste of time. Use short samples. Adobe Audition with dither: 997 Hz 8-bit -49.8 @@ -74,4 +75,4 @@ Frequency AP THD+N Script THD+N 19998 20.2% 56.3% (nasty intermodulation distortion) 20044 0.22% 0.30% So it's mostly accurate. Mostly. -
endolith revised this gist
Mar 9, 2010 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -29,7 +29,7 @@ def find_range(f, x): signal, fs, enc = flacread(filename) # 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 * blackmanharris(len(signal)) # Measure the total signal before filtering but after windowing -
endolith revised this gist
Mar 9, 2010 . 1 changed file with 2 additions and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -37,7 +37,8 @@ minima and throw away everything between them. It works for almost all cases, but on peaks with wider "skirts", it gets stuck at any notches. Should this be considered part of the peak or part of the noise (jitter)? By comparison, Audio Precision manual states "Bandreject Response typically –3 dB at 0.725 f0 & 1.38 f0", which is about 0.93 octaves. Also it computes the FFT for the entire sample, which is a waste of time. Use short samples. -
endolith revised this gist
Mar 9, 2010 . 1 changed file with 2 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -37,6 +37,8 @@ minima and throw away everything between them. It works for almost all cases, but on peaks with wider "skirts", it gets stuck at any notches. Should this be considered part of the peak or part of the noise (jitter)? By comparison, Audio Precision manual states "Bandreject Response typically –3 dB at 0.725 f0 & 1.38 f0", which is about 0.93 octaves. Also it computes the FFT for the entire sample, which is a waste of time. Use short samples. Adobe Audition with dither: -
endolith revised this gist
Mar 2, 2010 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -50,7 +50,7 @@ Reference 0.0% 0.0022% -93.3 Single-ended triode 5.0% 5.06% -25.9 Solid state 0.5% 0.51% -45.8 Comparing a test device on an Audio Precision System One 22 kHz filtered vs recorded into my 96 kHz 24-bit sound card and measured with this script: Frequency AP THD+N Script THD+N -
endolith revised this gist
Mar 2, 2010 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -35,7 +35,7 @@ f[i-width: i+width+1] = 0 Instead of a fixed width, it currently just tries to find the nearest local minima and throw away everything between them. It works for almost all cases, but on peaks with wider "skirts", it gets stuck at any notches. Should this be considered part of the peak or part of the noise (jitter)? Also it computes the FFT for the entire sample, which is a waste of time. Use short samples. -
endolith revised this gist
Dec 6, 2009 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -39,7 +39,7 @@ def find_range(f, x): # the signal by throwing away values between the nearest local minima f = rfft(windowed) i = argmax(abs(f)) print 'Frequency: %f Hz' % (fs * (i / len(windowed))) # Not exact lowermin, uppermin = find_range(abs(f), i) f[lowermin: uppermin] = 0 -
endolith revised this gist
Dec 6, 2009 . 1 changed file with 8 additions and 8 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -8,21 +8,21 @@ Depends on Audiolab and SciPy * http://www.ar.media.kyoto-u.ac.jp/members/david/softwares/audiolab/ * http://www.scipy.org/ Example of usage, with 997 Hz full-scale sine wave generated by Adobe Audition at 96 kHz, showing the 16-bit quantization distortion: > python thdcalculator.py "perfect 997 Hz no dither.flac" Frequency: 997.000000 Hz THD+N: 0.0016% or -96.1 dB (Is this right? Theoretical SNR of a FS sine is 1.761+6.02*16 = -98.09 dB. Close, at least.) According to the never-wrong Wikipedia: * THD is the fundamental alone vs the harmonics alone * THD+N is the entire signal (not just the fundamental) vs the entire signal with the fundamental notched out. (For low distortion, the difference between the entire signal and the fundamental is negligible.) The primary problem with the current script is that I don't know how much of the surrounding region of the peak to throw away. Probably the way to match -
endolith revised this gist
Dec 6, 2009 . 2 changed files with 4 additions and 5 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -50,8 +50,8 @@ Reference 0.0% 0.0022% -93.3 Single-ended triode 5.0% 5.06% -25.9 Solid state 0.5% 0.51% -45.8 Comparing a test device on an Audio Precision 22 kHz filtered vs recorded into my 96 kHz 24-bit sound card and measured with this script: Frequency AP THD+N Script THD+N 40 1.00% 1.04% @@ -68,7 +68,7 @@ Frequency AP THD+N Script THD+N 10036 0.051% 0.068% 10723 8.2% 9.3% 13640 12.2% 16.8% 19998 20.2% 56.3% (nasty intermodulation distortion) 20044 0.22% 0.30% So it's mostly accurate. Mostly. This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -6,8 +6,7 @@ from numpy import argmax, sqrt, mean, absolute, arange, log10 def rms_flat(a): """Return the root mean square of all the elements of *a*, flattened out. """ return sqrt(mean(absolute(a)**2)) -
endolith revised this gist
Dec 6, 2009 . 1 changed file with 2 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -37,6 +37,8 @@ minima and throw away everything between them. It works for almost all cases, but on peaks with wider "skirts", it gets stuck at any notches. Should this be considered part of the peak or part of the noise? Also it computes the FFT for the entire sample, which is a waste of time. Use short samples. Adobe Audition with dither: 997 Hz 8-bit -49.8 997 Hz 16-bit -93.4 -
endolith revised this gist
Dec 6, 2009 . 2 changed files with 46 additions and 48 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -4,8 +4,11 @@ Measures the total harmonic distortion plus noise (THD+N) for a given input signal, by guessing the fundamental frequency (finding the peak in the FFT), and notching it out in the frequency domain. Depends on Audiolab and SciPy * http://www.ar.media.kyoto-u.ac.jp/members/david/softwares/audiolab/ * http://www.scipy.org/ According to the never-wrong Wikipedia: * THD is the fundamental alone vs the harmonics alone * THD+N is the entire signal (not just the fundamental) vs the entire signal with the fundamental notched out. (For low distortion, the difference between @@ -21,6 +24,19 @@ THD+N: 0.0016% or -96.1 dB Is this right? Theoretical SNR of a FS sine is 1.761+6.02*16 = -98.09 dB. Close, at least. The primary problem with the current script is that I don't know how much of the surrounding region of the peak to throw away. Probably the way to match other test equipment is to just calculate the width of a certain bandwidth, but is that really ideal? width = 50 f[i-width: i+width+1] = 0 Instead of a fixed width, it currently just tries to find the nearest local minima and throw away everything between them. It works for almost all cases, but on peaks with wider "skirts", it gets stuck at any notches. Should this be considered part of the peak or part of the noise? Adobe Audition with dither: 997 Hz 8-bit -49.8 997 Hz 16-bit -93.4 @@ -40,15 +56,17 @@ Frequency AP THD+N Script THD+N 100 0.15% 0.19% 100 0.15% 0.14% 140 0.15% 0.17% 440 0.056% 0.057% 961 0.062% 0.067% 1021 0.080% 0.082% 1440 0.042% 0.041% 1483 0.15% 0.15% 4440 0.048% 0.046% 9974 7.1% 7.8% 10036 0.051% 0.068% 10723 8.2% 9.3% 13640 12.2% 16.8% 19998 20.2% 56.3% 20044 0.22% 0.30% So it's mostly accurate. Mostly. This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,11 +1,9 @@ from __future__ import division import sys from scikits.audiolab import flacread from scipy.signal import blackmanharris from numpy.fft import rfft, irfft from numpy import argmax, sqrt, mean, absolute, arange, log10 def rms_flat(a): """ @@ -14,55 +12,37 @@ def rms_flat(a): """ return sqrt(mean(absolute(a)**2)) def find_range(f, x): """Find range between nearest local minima from peak at index x """ for i in arange(x+1, len(f)): if f[i+1] >= f[i]: uppermin = i break for i in arange(x-1, 0, -1): if f[i] <= f[i-1]: lowermin = i + 1 break return (lowermin, uppermin) filename = sys.argv[1] signal, fs, enc = flacread(filename) # Get rid of DC and window the signal signal -= mean(signal) windowed = signal * blackmanharris(len(signal)) # Measure the total signal before filtering but after windowing total_rms = rms_flat(windowed) # Find the peak of the frequency spectrum (fundamental frequency), and filter # the signal by throwing away values between the nearest local minima f = rfft(windowed) i = argmax(abs(f)) print 'Frequency: %f Hz' % (fs * (i / len(windowed))) lowermin, uppermin = find_range(abs(f), i) f[lowermin: uppermin] = 0 # Transform noise back into the signal domain and measure it # Could probably calculate the RMS directly in the frequency domain instead -
endolith revised this gist
Dec 5, 2009 . 1 changed file with 12 additions and 5 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,19 +1,25 @@ Somewhat crude THD+N calculator in Python Measures the total harmonic distortion plus noise (THD+N) for a given input signal, by guessing the fundamental frequency (finding the peak in the FFT), and notching it out in the frequency domain. According to the never-wrong Wikipedia: * THD is the fundamental alone vs the harmonics alone * THD+N is the entire signal (not just the fundamental) vs the entire signal with the fundamental notched out. (For low distortion, the difference between the entire signal and the fundamental is negligible.) Example of usage, with 997 Hz full-scale sine wave generated by Adobe Audition at 96 kHz, showing the 16-bit quantization distortion: > python thdcalculator.py "perfect 997 Hz no dither.flac" Frequency: 997.000000 Hz THD+N: 0.0016% or -96.1 dB Is this right? Theoretical SNR of a FS sine is 1.761+6.02*16 = -98.09 dB. Close, at least. Adobe Audition with dither: 997 Hz 8-bit -49.8 @@ -26,7 +32,8 @@ Reference 0.0% 0.0022% -93.3 Single-ended triode 5.0% 5.06% -25.9 Solid state 0.5% 0.51% -45.8 Comparing a test device on an Audio Precision vs recorded into my 24-bit sound card with this script: Frequency AP THD+N Script THD+N 40 1.00% 1.04% -
endolith revised this gist
Dec 5, 2009 . 1 changed file with 1 addition and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -15,6 +15,7 @@ THD+N: 0.0016% or -96.1 dB Is this right? Theoretical SNR of a FS sine is 1.761+6.02*16 = -98.09 dB. Close, at least. Adobe Audition with dither: 997 Hz 8-bit -49.8 997 Hz 16-bit -93.4 997 Hz 32-bit -143.9 -
endolith revised this gist
Dec 5, 2009 . 1 changed file with 21 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -15,6 +15,10 @@ THD+N: 0.0016% or -96.1 dB Is this right? Theoretical SNR of a FS sine is 1.761+6.02*16 = -98.09 dB. Close, at least. 997 Hz 8-bit -49.8 997 Hz 16-bit -93.4 997 Hz 32-bit -143.9 Art Ludwig's Sound Files (http://members.cox.net/artludwig/): File Claimed Measured (dB) Reference 0.0% 0.0022% -93.3 @@ -23,3 +27,20 @@ Solid state 0.5% 0.51% -45.8 Comparing a test device on an Audio Precision vs recorded into my 24-bit sound card with this script: Frequency AP THD+N Script THD+N 40 1.00% 1.04% 100 0.15% 0.19% 100 0.15% 0.14% 140 0.15% 0.17% 440 0.06% 0.06% 961 0.06% 0.07% 1021 0.08% 0.09% 1440 0.04% 0.05% 1483 0.15% 0.15% 4440 0.05% 0.05% 9974 7.1% 7.8% 10036 0.05% 0.12% 10723 8.2% 9.3% 20044 0.22% 0.60% 13640 12.2% 16.8% 19998 20.2% 56.3% -
endolith revised this gist
Dec 5, 2009 . 2 changed files with 11 additions and 12 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -7,21 +7,19 @@ According to the never-wrong Wikipedia: * THD is the fundamental alone vs the harmonics alone * THD+N is the entire signal (not just the fundamental) vs the entire signal with the fundamental notched out. (For low distortion, the difference between the entire signal and the fundamental is negligible.) Example of usage, with 997 Hz full-scale sine wave generated by Adobe Audition at 96 kHz, showing the 16-bit quantization distortion: > python thdcalculator.py "perfect 997 Hz no dither.flac" Frequency: 997.000000 Hz THD+N: 0.0016% or -96.1 dB Is this right? Theoretical SNR of a FS sine is 1.761+6.02*16 = -98.09 dB. Close, at least. Art Ludwig's Sound Files (http://members.cox.net/artludwig/): File Claimed Measured (dB) Reference 0.0% 0.0022% -93.3 Single-ended triode 5.0% 5.06% -25.9 Solid state 0.5% 0.51% -45.8 Comparing a test device on an Audio Precision vs recorded into my 24-bit sound card with this script: This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -61,7 +61,8 @@ def parabolic(f, x): print 'Frequency: %f Hz' % (fs * true_i / len(windowed)) # TODO: What's the right number of coefficients to use? Probably depends on sample length, frequency? windowing etc. width = 10 f[i-width: i+width+1] = 0 # Transform noise back into the signal domain and measure it # Could probably calculate the RMS directly in the frequency domain instead -
endolith revised this gist
Dec 4, 2009 . 1 changed file with 2 additions and 2 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -58,7 +58,7 @@ def parabolic(f, x): # throwing away the region around it i = argmax(abs(f)) # absolute? true_i = parabolic(abs(f), i)[0] print 'Frequency: %f Hz' % (fs * true_i / len(windowed)) # TODO: What's the right number of coefficients to use? Probably depends on sample length, frequency? windowing etc. f[i-10: i+11] = 0 @@ -67,4 +67,4 @@ def parabolic(f, x): # Could probably calculate the RMS directly in the frequency domain instead noise = irfft(f) THDN = rms_flat(noise) / total_rms print "THD+N: %.4f%% or %.1f dB" % (THDN * 100, 20 * log10(THDN))
NewerOlder