From bd3fece8e2624da0b22bd5d53752929c6b0b83db Mon Sep 17 00:00:00 2001 From: Seppo Ingalsuo Date: Tue, 22 Nov 2022 20:54:48 +0200 Subject: [PATCH 1/5] Tools: Add more robustness to check_volume_levels.m measure This patch delays the check window if the gain settling seems to be still happening in the beginning of level check window. The settling is detected with differential of gain. The window means acceptance criteria over time as gain in decibels. E.g. x milliseconds to y milliseconds, measured gain within a +/- b decibels. The average gain from the measure window is computed and compare to required. Any fail will fail the entire test. The differential of measured gain shows if the gain is constant or changing. The added code is shifting the measurement window to later in time if the gain is not constant in in the beginning. The delay in gain settling can be caused by random alsamixer controls apply delay. Also a very long gain ramp makes the settling to appear later. Signed-off-by: Seppo Ingalsuo --- tools/check_volume_levels.m | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tools/check_volume_levels.m b/tools/check_volume_levels.m index 8e2b1e5b..8f3c4361 100755 --- a/tools/check_volume_levels.m +++ b/tools/check_volume_levels.m @@ -187,14 +187,36 @@ function plot_levels(meas, tc, lm) function pass = check_levels(meas, tc, lm, verbose) pass = 1; + dg_tol = 0.1; gains = meas.levels - lm.sine_dbfs; sv = size(tc.volumes); for j = 1:sv(2) for i = 1:sv(1) + % Initial location to test ts = tc.vctimes(i)+tc.meas(1); te = tc.vctimes(i)+tc.meas(2); idx0 = find(meas.t < te); idx = find(meas.t(idx0) > ts); + + % Delay if settled gain is later in the window, + % this adds more robustness to test for controls + % apply delay. + dg = diff(gains(idx,j)); + if max(abs(dg)) > dg_tol + n_idx = length(idx); + dg_rev = dg(end:-1:1); + idx_add = length(dg) - find(abs(dg_rev) > dg_tol, 1, 'first') + 1; + idx = idx + idx_add; + if idx(end) > size(gains, 1) + idx = idx(1):size(gains, 1); + end + if idx(1) > size(gains, 1) || length(idx) < 0.5 * n_idx + fprintf(1, 'Channel %d controls impact is delayed too much ', j); + fprintf(1, 'from %4.1f - %4.1fs\n', ts, te); + pass = 0; + return; + end + end avg_gain = mean(gains(idx, j)); max_gain = tc.volumes(i,j) + tc.vtol; min_gain = tc.volumes(i,j) - tc.vtol; From 46ac32627edf90ac03850aa4e82f1adc42fafedc Mon Sep 17 00:00:00 2001 From: Seppo Ingalsuo Date: Tue, 22 Nov 2022 20:57:36 +0200 Subject: [PATCH 2/5] Tools: Add option to plot levels with check_volume_levels.m If a test fails, it's easiest to examine the result graphically. Pass to command line the failed wav files names and last argument set to one to see the observed levels. Signed-off-by: Seppo Ingalsuo --- tools/check_volume_levels.m | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/tools/check_volume_levels.m b/tools/check_volume_levels.m index 8f3c4361..4cebcd87 100755 --- a/tools/check_volume_levels.m +++ b/tools/check_volume_levels.m @@ -1,4 +1,4 @@ -function check_volume_levels(cmd, fn1, fn2, fn3) +function check_volume_levels(cmd, fn1, fn2, fn3, do_plot) % check_volume_levels(cmd, fn1, fn2, fn3) % @@ -7,15 +7,21 @@ function check_volume_levels(cmd, fn1, fn2, fn3) % fn1 - File name for sine wave to generate or first record file name to analyze % fn2 - File name to analyze 2nd % fn3 - File name to analyze 3rd +% do_plot - Plot figure of levels if 1, defaults to 0 % % E.g. % check_volume_levels('generate','sine.wav'); % check_volume_levels('measure','rec1.wav','rec2.wav','rec3.wav'); +% check_volume_levels('measure','rec1.wav','rec2.wav','rec3.wav', 1); % SPDX-License-Identifier: BSD-3-Clause % Copyright(c) 2016 Intel Corporation. All rights reserved. % Author: Seppo Ingalsuo + if nargin < 5 + do_plot = 0; + end + addpath('../../sof/tools/test/audio/std_utils'); addpath('../../sof/tools/test/audio/test_utils'); @@ -32,7 +38,7 @@ function check_volume_levels(cmd, fn1, fn2, fn3) error('FAIL'); end case 'measure' - pass = measure(fn1, fn2, fn3); + pass = measure(fn1, fn2, fn3, do_plot); if pass fprintf(1, 'PASS\n'); else @@ -64,7 +70,7 @@ function check_volume_levels(cmd, fn1, fn2, fn3) end -function pass = measure(fn1, fn2, fn3) +function pass = measure(fn1, fn2, fn3, do_plot) % General test defaults lm.tgrid = 5e-3; % Return level per every 5ms @@ -87,7 +93,7 @@ function check_volume_levels(cmd, fn1, fn2, fn3) t1.vtol = 0.5; % Pass test with max +/- 0.5 dB mismatch % Check test 1 - pass1 = level_vs_time_checker(fn1, t1, lm, '1/3'); + pass1 = level_vs_time_checker(fn1, t1, lm, '1/3', do_plot); % Default gains for test 2 m1 = [vmut vnom vnom vmut]; @@ -100,7 +106,7 @@ function check_volume_levels(cmd, fn1, fn2, fn3) t2.vtol = t1.vtol; % Same as previous % Check test 2 - pass2 = level_vs_time_checker(fn2, t2, lm, '2/3'); + pass2 = level_vs_time_checker(fn2, t2, lm, '2/3', do_plot); % Default gains for test 3 vol_ch1 = [ vmut vmut m2(1) vnom ]; @@ -111,7 +117,7 @@ function check_volume_levels(cmd, fn1, fn2, fn3) t3.vtol = t1.vtol; % Same as previous % Check test 3 - pass3 = level_vs_time_checker(fn3, t3, lm, '3/3'); + pass3 = level_vs_time_checker(fn3, t3, lm, '3/3', do_plot); if pass1 == 1 && pass2 == 1 && pass3 == 1 pass = 1; @@ -121,11 +127,13 @@ function check_volume_levels(cmd, fn1, fn2, fn3) end -function pass = level_vs_time_checker(fn, tc, lm, id) +function pass = level_vs_time_checker(fn, tc, lm, id, do_plot) fprintf(1, 'File %s:\n', fn); lev = level_vs_time(fn, lm); - %plot_levels(lev, tc, lm); + if do_plot + plot_levels(lev, tc, lm); + end pass = check_levels(lev, tc, lm, 1); if pass fprintf(1, 'pass (%s)\n', id); @@ -183,6 +191,7 @@ function plot_levels(meas, tc, lm) hold off; xlabel('Time (s)'); ylabel('Gain (dB)'); + grid on; end function pass = check_levels(meas, tc, lm, verbose) From 902ed6d8d5a1f0f528608c957b49a34a1660a6fb Mon Sep 17 00:00:00 2001 From: Seppo Ingalsuo Date: Tue, 22 Nov 2022 20:58:56 +0200 Subject: [PATCH 3/5] Tools: Fix Matlab compatibility for check_volume_levels.m Matlab requires switch - end, endswitch is not accepted. Signed-off-by: Seppo Ingalsuo --- tools/check_volume_levels.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/check_volume_levels.m b/tools/check_volume_levels.m index 4cebcd87..a93ef3f0 100755 --- a/tools/check_volume_levels.m +++ b/tools/check_volume_levels.m @@ -46,7 +46,7 @@ function check_volume_levels(cmd, fn1, fn2, fn3, do_plot) end otherwise error('Invalid cmd') - endswitch + end end From 1cd54e234988d7644abe42d926fbbdd21d2defbd Mon Sep 17 00:00:00 2001 From: Seppo Ingalsuo Date: Wed, 7 Dec 2022 19:44:40 +0200 Subject: [PATCH 4/5] Tools: Remove dependency to SOF from check_volume_levels.m This patch adds local functions multitone() and level_dbfs() to script check_volume_levels.m to avoid need for SOF repository. The added functions are small and simple so the duplication is not an issue. Signed-off-by: Seppo Ingalsuo --- tools/check_volume_levels.m | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/tools/check_volume_levels.m b/tools/check_volume_levels.m index a93ef3f0..65d4118d 100755 --- a/tools/check_volume_levels.m +++ b/tools/check_volume_levels.m @@ -22,9 +22,6 @@ function check_volume_levels(cmd, fn1, fn2, fn3, do_plot) do_plot = 0; end - addpath('../../sof/tools/test/audio/std_utils'); - addpath('../../sof/tools/test/audio/test_utils'); - if exist('OCTAVE_VERSION', 'builtin') pkg load signal end @@ -282,3 +279,29 @@ function plot_levels(meas, tc, lm) y(:,j) = filter(b, a, x(:,j)); end end + +% This function is copy of +% sof/tools/test/audio/test_utils/multitone.m +function x = multitone(fs, f, amp, tlength) + n = round(fs * tlength); + t = (0 : n - 1) / fs; + nf = length(f); + if nf > 1 + ph = rand(nf, 1) * 2 * pi; + else + ph = 0; + end + + x = zeros(n, 1); + for i = 1 : length(f) + x = x + amp(i) * sin(2 * pi * f(i) * t + ph(i))'; + end +end + +% This function is copy of +% sof/tools/test/audio/std_utils/level_dbfs.m +function dbfs = level_dbfs(x) + %% Reference AES 17 3.12.3 + level_ms = mean(x .^ 2); + dbfs = 10 * log10(level_ms + 1e-20) + 20 * log10(sqrt(2)); +end From 5eec1168b4ad26f19a744d69a00ea0fbe620eacf Mon Sep 17 00:00:00 2001 From: Seppo Ingalsuo Date: Tue, 7 Mar 2023 14:12:25 +0200 Subject: [PATCH 5/5] Tools: Code style changes to check_volume_levels.m There's no functional changes, only added blanks between arithmetic operations, comma separated lists, and ":" operator. Signed-off-by: Seppo Ingalsuo --- tools/check_volume_levels.m | 72 ++++++++++++++++++------------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/tools/check_volume_levels.m b/tools/check_volume_levels.m index 65d4118d..0fc8c203 100755 --- a/tools/check_volume_levels.m +++ b/tools/check_volume_levels.m @@ -10,9 +10,9 @@ function check_volume_levels(cmd, fn1, fn2, fn3, do_plot) % do_plot - Plot figure of levels if 1, defaults to 0 % % E.g. -% check_volume_levels('generate','sine.wav'); -% check_volume_levels('measure','rec1.wav','rec2.wav','rec3.wav'); -% check_volume_levels('measure','rec1.wav','rec2.wav','rec3.wav', 1); +% check_volume_levels('generate', 'sine.wav'); +% check_volume_levels('measure', 'rec1.wav', 'rec2.wav', 'rec3.wav'); +% check_volume_levels('measure', 'rec1.wav', 'rec2.wav', 'rec3.wav', 1); % SPDX-License-Identifier: BSD-3-Clause % Copyright(c) 2016 Intel Corporation. All rights reserved. @@ -55,13 +55,13 @@ function check_volume_levels(cmd, fn1, fn2, fn3, do_plot) fs = 48e3; f1 = 701; f2 = 1297; - a = 10^(-40/20); + a = 10 ^ (-40 / 20); t = 60; x1 = multitone(fs, f1, a, t); x2 = multitone(fs, f2, a, t); x = [x1'; x2']'; sx = size(x); - d = (rand(sx(1), sx(2)) - 0.5)/2^15; + d = (rand(sx(1), sx(2)) - 0.5) / 2 ^ 15; audiowrite(fn, x + d, fs); pass = 1; end @@ -90,7 +90,7 @@ function check_volume_levels(cmd, fn1, fn2, fn3, do_plot) t1.vtol = 0.5; % Pass test with max +/- 0.5 dB mismatch % Check test 1 - pass1 = level_vs_time_checker(fn1, t1, lm, '1/3', do_plot); + pass1 = level_vs_time_checker(fn1, t1, lm, '1 / 3', do_plot); % Default gains for test 2 m1 = [vmut vnom vnom vmut]; @@ -103,7 +103,7 @@ function check_volume_levels(cmd, fn1, fn2, fn3, do_plot) t2.vtol = t1.vtol; % Same as previous % Check test 2 - pass2 = level_vs_time_checker(fn2, t2, lm, '2/3', do_plot); + pass2 = level_vs_time_checker(fn2, t2, lm, '2 / 3', do_plot); % Default gains for test 3 vol_ch1 = [ vmut vmut m2(1) vnom ]; @@ -114,7 +114,7 @@ function check_volume_levels(cmd, fn1, fn2, fn3, do_plot) t3.vtol = t1.vtol; % Same as previous % Check test 3 - pass3 = level_vs_time_checker(fn3, t3, lm, '3/3', do_plot); + pass3 = level_vs_time_checker(fn3, t3, lm, '3 / 3', do_plot); if pass1 == 1 && pass2 == 1 && pass3 == 1 pass = 1; @@ -139,7 +139,7 @@ function check_volume_levels(cmd, fn1, fn2, fn3, do_plot) % Swapped channels? sine_freqs_orig = lm.sine_freqs; - lm.sine_freqs = sine_freqs_orig(end:-1:1); + lm.sine_freqs = sine_freqs_orig(end : -1 : 1); lev = level_vs_time(fn, lm); pass_test = check_levels(lev, tc, lm, 0); if pass_test @@ -150,7 +150,7 @@ function check_volume_levels(cmd, fn1, fn2, fn3, do_plot) % Swapped controls? lm.sine_freqs = sine_freqs_orig; volumes_orig = tc.volumes; - tc.volumes = volumes_orig(:, end:-1:1); + tc.volumes = volumes_orig(:, end : -1 : 1); lev = level_vs_time(fn, lm); pass_test = check_levels(lev, tc, lm, 0); if pass_test @@ -159,7 +159,7 @@ function check_volume_levels(cmd, fn1, fn2, fn3, do_plot) end % Swapped controls and swapped channels - lm.sine_freqs = sine_freqs_orig(end:-1:1); + lm.sine_freqs = sine_freqs_orig(end : -1 : 1); lev = level_vs_time(fn, lm); pass_test = check_levels(lev, tc, lm, 0); if pass_test @@ -175,13 +175,13 @@ function plot_levels(meas, tc, lm) sv = size(tc.volumes); hold on; - for j = 1:sv(2) - for i = 1:sv(1) - plot([tc.vctimes(i)+tc.meas(1) tc.vctimes(i)+tc.meas(2)], ... - [tc.volumes(i,j)+tc.vtol tc.volumes(i,j)+tc.vtol], 'r--'); - if tc.volumes(i,j) > -100 - plot([tc.vctimes(i)+tc.meas(1) tc.vctimes(i)+tc.meas(2)], ... - [tc.volumes(i,j)-tc.vtol tc.volumes(i,j)-tc.vtol], 'r--'); + for j = 1 : sv(2) + for i = 1 : sv(1) + plot([tc.vctimes(i) + tc.meas(1) tc.vctimes(i) + tc.meas(2)], ... + [tc.volumes(i, j) + tc.vtol tc.volumes(i, j) + tc.vtol], 'r--'); + if tc.volumes(i, j) > -100 + plot([tc.vctimes(i) + tc.meas(1) tc.vctimes(i) + tc.meas(2)], ... + [tc.volumes(i, j) - tc.vtol tc.volumes(i, j) - tc.vtol], 'r--'); end end end @@ -196,25 +196,25 @@ function plot_levels(meas, tc, lm) dg_tol = 0.1; gains = meas.levels - lm.sine_dbfs; sv = size(tc.volumes); - for j = 1:sv(2) - for i = 1:sv(1) + for j = 1 : sv(2) + for i = 1 : sv(1) % Initial location to test - ts = tc.vctimes(i)+tc.meas(1); - te = tc.vctimes(i)+tc.meas(2); + ts = tc.vctimes(i) + tc.meas(1); + te = tc.vctimes(i) + tc.meas(2); idx0 = find(meas.t < te); idx = find(meas.t(idx0) > ts); % Delay if settled gain is later in the window, % this adds more robustness to test for controls % apply delay. - dg = diff(gains(idx,j)); + dg = diff(gains(idx, j)); if max(abs(dg)) > dg_tol n_idx = length(idx); - dg_rev = dg(end:-1:1); + dg_rev = dg(end : -1 : 1); idx_add = length(dg) - find(abs(dg_rev) > dg_tol, 1, 'first') + 1; idx = idx + idx_add; if idx(end) > size(gains, 1) - idx = idx(1):size(gains, 1); + idx = idx(1) : size(gains, 1); end if idx(1) > size(gains, 1) || length(idx) < 0.5 * n_idx fprintf(1, 'Channel %d controls impact is delayed too much ', j); @@ -224,8 +224,8 @@ function plot_levels(meas, tc, lm) end end avg_gain = mean(gains(idx, j)); - max_gain = tc.volumes(i,j) + tc.vtol; - min_gain = tc.volumes(i,j) - tc.vtol; + max_gain = tc.volumes(i, j) + tc.vtol; + min_gain = tc.volumes(i, j) - tc.vtol; if avg_gain > max_gain if verbose fprintf(1, 'Channel %d Failed upper gain limit at ', j); @@ -234,7 +234,7 @@ function plot_levels(meas, tc, lm) end pass = 0; end - if tc.volumes(i,j) > -100 + if tc.volumes(i, j) > -100 if avg_gain < min_gain if verbose fprintf(1, 'Channel %d failed lower gain limit at ', j); @@ -259,24 +259,24 @@ function plot_levels(meas, tc, lm) ngrid = lm.tgrid * fs; nlength = lm.tlength * fs; nmax = nlev - round(nlength / ngrid) + 1; - ret.t = (0:(nmax-1)) * lm.tgrid; + ret.t = (0 : (nmax - 1)) * lm.tgrid; ret.levels = zeros(nmax, nch); - for i = 1:nmax + for i = 1 : nmax i1 = floor((i - 1) * ngrid + 1); i2 = floor(i1 + nlength -1); - ret.levels(i, :) = level_dbfs(x(i1:i2, :)); + ret.levels(i, :) = level_dbfs(x(i1 : i2, :)); end - ret.levels_lin = 10.^(ret.levels/20); + ret.levels_lin = 10 .^ (ret.levels / 20); end function y = bandpass_filter(x, f, fs) sx = size(x); y = zeros(sx(1), sx(2)); c1 = 0.8; - c2 = 1/c1; - for j = 1:sx(2) - [b, a] = butter(4, 2*[c1*f(j) c2*f(j)]/fs); - y(:,j) = filter(b, a, x(:,j)); + c2 = 1 / c1; + for j = 1 : sx(2) + [b, a] = butter(4, 2 *[c1 * f(j) c2 * f(j)] / fs); + y(:, j) = filter(b, a, x(:, j)); end end