Repository navigation
wavetool.py: add THD+N and RMS calculation for wave analysis #524
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,6 +21,9 @@ | |
| # is wrong with firmware scheduler. | ||
| SMART_AMP_DELAY_THRESHOLD = 8 | ||
|
|
||
| # used to avoid zero division | ||
| eps = 0.000000000001 | ||
|
|
||
| # Module level global variable which will store command line parameters later | ||
| cmd = None | ||
|
|
||
|
|
@@ -112,6 +115,8 @@ def do_wave_analysis(): | |
| analyze_wav_smart_amp(wave, fs_wav) | ||
| if cmd.analyze == 'wov': | ||
| analyze_wav_wov(wave, fs_wav) | ||
|
aiChaoSONG marked this conversation as resolved.
Resolved
|
||
| if cmd.analyze == 'thdn': | ||
| analyze_wav_thdn(wave, fs_wav) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you add DC offset calculation? Take average value of the clip to analyse and report as dBFS (repeat for every channel). You can omit the start transient. The fail criteria default should be > -60 dBFS. With 16 bit audio it should be normally around -90 dBFS. |
||
|
|
||
| # remove digital zeros in two sides | ||
| def trim_wave(wave): | ||
|
|
@@ -205,6 +210,17 @@ def analyze_wav_wov(wave, fs): | |
| sys.exit(1002) | ||
| print('wave analysis result: PASSED') | ||
|
|
||
| def analyze_wav_thdn(wave, fs_wav): | ||
| wave = normalize(wave) | ||
| check_rms(wave) | ||
| thdn = calc_thdn(wave, fs_wav, cmd.freq[0]) | ||
| result = thdn < cmd.threshold | ||
| print('THD+N of specified wave is %s dB' % thdn) | ||
| if not np.all(result): | ||
| print('THD+N too high, wave analysis result: FAILED') | ||
| sys.exit(1003) | ||
|
aiChaoSONG marked this conversation as resolved.
Resolved
|
||
| print('wave analysis result: PASSED') | ||
|
|
||
| def calc_thdn(wave, fs, freq): | ||
| """ | ||
| This function calculates Total Harmonic Distortion plus Noise (THD+N) of a wave. | ||
|
|
@@ -217,14 +233,36 @@ def calc_thdn(wave, fs, freq): | |
|
|
||
| Returns | ||
| ---------- | ||
| A list of THD+N values for each channel | ||
| A list of THD+N values for each channel, unit: dB | ||
| """ | ||
| # skip 20ms to avoid the pulse due to filtering | ||
| time_skip = 0.02 | ||
| samples_skip = int(time_skip * fs) | ||
| filtered = stdnotch(wave, freq, fs) | ||
| filtered_cut = filtered[samples_skip:,:] | ||
| return 10 * np.log10(np.mean(np.power(filtered_cut, 2), axis=0)) | ||
| filtered_cut = filtered[samples_skip:] | ||
| return 10 * np.log10(np.mean(np.power(filtered_cut, 2), axis=0) + eps) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we should call this "THD+N ratio" since it was measured with sine wave input. Extracts from AES spec: "The test signal shall be a sine wave. The analyzer shall include the standard low-pass filter (see 5.2.5). The total and "The test signal level shall be a sine wave at -1 dB relative to the maximum input level (see 6.2.1). " So, the used sine level should be subtracted from the result. E.g. -81 - (-1) dB gives -80 dB. Edit: Thinking more I think the "total signal level" above means the RMS level level of output (dBFS) with all the distortion and noise included, so it should be measured too in step. It's simpler to do since you don't need to pass or hard code it here.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I think we may not use the THD+N ratio here, because our test signal is not a standard -1 dB sine wave. our loopback wave varies in level, so an absolute value (thdn) is better than a relative value (thdn ratio) I just realized that we may get a lower THD+N from a wave with lower level, the THD+N ratio is less impacted by this. Thanks.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The problem without ratio is that a muted / a lot attenuated test might achieve a really good THD+N value. You need to set the limit to such that an OK performing system passes it. I would never pass a result worse than -60 dB (equivalent to 10 bits performance - our implementation is 16/24/32 bits).
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @singalsu For a good quality sine wave with -30dB level, and -80dB thdn, the THD+N ratio is on -50dB. it is hard to maintain a 0 dB volume control in our loopback route, how can we deal with this condition? maybe we should keep the calculation of thdn and thdn ratio, and let user to choose what they want to use?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. With current test system (headphone out -> headset adapter mic in) I think it's not expected to get any better performance. Could the pass criteria be something like -48 dB until the CI hardware can be upgraded? Testing it is much better than nothing though small issues will be left unnoticed. The THD+N alone is not good because a broken system can get a good result. @mengdonglin The -50 dB THD+N ratio is performance of a 80's C-cassette player. Please try to escalate the need for better audio validation hardware. We really should be able to use something like -70 to -80 dB THD+N ratio as automatic test pass/fail criteria. |
||
|
|
||
| def check_rms(wave): | ||
| dbfs = calc_rms(wave) | ||
| print('RMS of specified wave is %s dB' % dbfs) | ||
| dbfs_result = dbfs > cmd.zero_threshold | ||
| if not np.all(dbfs_result): | ||
| print("Wave level too low, try to increase volume!!!") | ||
| print("Valid wave level should be bigger than %f dBFS" % cmd.zero_threshold) | ||
| sys.exit(1003) | ||
|
|
||
| def calc_rms(wave): | ||
| """ | ||
| This function calculates Root Mean Square (RMS) value of a wave, the value is used | ||
| to identify wave level. | ||
| Parameters | ||
| ---------- | ||
| wave: input wave data | ||
| Returns | ||
| ---------- | ||
| A list of RMS values for each channel, unit: dBFS | ||
| """ | ||
| return 10 * np.log10(np.mean(np.power(wave,2), axis=0) + eps) | ||
|
|
||
| def parse_cmdline(): | ||
| parser = argparse.ArgumentParser(add_help=True, formatter_class=argparse.RawTextHelpFormatter, | ||
|
|
@@ -242,7 +280,7 @@ def parse_cmdline(): | |
| help='sample bits of generated wave') | ||
| parser.add_argument('-o', '--output', type=str, help='path to store generated files', default='.') | ||
| # wave comparison arguments | ||
| parser.add_argument('-a', '--analyze', type=str, choices=['smart_amp', 'wov'], help='analyze recorded wave to give case verdict') | ||
| parser.add_argument('-a', '--analyze', type=str, choices=['smart_amp', 'wov', 'thdn'], help='analyze recorded wave to give case verdict') | ||
| parser.add_argument('-R', '--recorded_wave', type=str, help='path of recorded wave') | ||
| parser.add_argument('-Z', '--zero_threshold', type=float, default=-50.3, help='zero threshold in dBFS') | ||
| parser.add_argument('-H', '--hb_time', type=float, default=2.1, help='history buffer size') | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.