Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 42 additions & 4 deletions tools/wavetool.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
# is wrong with firmware scheduler.
SMART_AMP_DELAY_THRESHOLD = 8

# used to avoid zero division
eps = 0.000000000001
Comment thread
aiChaoSONG marked this conversation as resolved.
Resolved

# Module level global variable which will store command line parameters later
cmd = None

Expand Down Expand Up @@ -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)
Comment thread
aiChaoSONG marked this conversation as resolved.
Resolved
if cmd.analyze == 'thdn':
analyze_wav_thdn(wave, fs_wav)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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):
Expand Down Expand Up @@ -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)
Comment thread
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.
Expand All @@ -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)

@singalsu singalsu Nov 19, 2020 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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
signal level at the EUT output is measured and recorded. The fundamental component of the test signal present
in the output shall be removed with the standard notch filter (see 5.2.8), and the unweighted level of the residual
shall be measured. The THD+N ratio shall be the difference between the residual level and the total signal
level, expressed in dB."

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.

@aiChaoSONG aiChaoSONG Nov 20, 2020 •

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test signal level shall be a sine wave at -1 dB relative to the maximum input level

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.

@singalsu singalsu Nov 20, 2020 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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).

@aiChaoSONG aiChaoSONG Nov 24, 2020 •

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The 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?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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,
Expand All @@ -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')
Expand Down