Skip to content
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

Tools: Add more robustness to check_volume_levels.m measure #985

Merged
Merged
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
134 changes: 94 additions & 40 deletions tools/check_volume_levels.m
Original file line number Diff line number Diff line change
@@ -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)
%
Expand All @@ -7,17 +7,20 @@ 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('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 <[email protected]>

addpath('../../sof/tools/test/audio/std_utils');
addpath('../../sof/tools/test/audio/test_utils');
if nargin < 5
do_plot = 0;
end

if exist('OCTAVE_VERSION', 'builtin')
pkg load signal
Expand All @@ -32,15 +35,15 @@ 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
error('FAIL');
end
otherwise
error('Invalid cmd')
endswitch
end

end

Expand All @@ -52,19 +55,19 @@ function check_volume_levels(cmd, fn1, fn2, fn3)
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


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
Expand All @@ -87,7 +90,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];
Expand All @@ -100,7 +103,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 ];
Expand All @@ -111,7 +114,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;
Expand All @@ -121,11 +124,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);
Expand All @@ -134,7 +139,7 @@ function check_volume_levels(cmd, fn1, fn2, fn3)

% 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
Expand All @@ -145,7 +150,7 @@ function check_volume_levels(cmd, fn1, fn2, fn3)
% 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
Expand All @@ -154,7 +159,7 @@ function check_volume_levels(cmd, fn1, fn2, fn3)
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
Expand All @@ -170,34 +175,57 @@ 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
hold off;
xlabel('Time (s)');
ylabel('Gain (dB)');
grid on;
end

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)
ts = tc.vctimes(i)+tc.meas(1);
te = tc.vctimes(i)+tc.meas(2);
for j = 1 : sv(2)
for i = 1 : sv(1)
% Initial location to test

Choose a reason for hiding this comment

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

could you please add a space between ts = tc.vctimes(i)+tc.meas(1); as ts = tc.vctimes(i) + tc.meas(1);

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;

Choose a reason for hiding this comment

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

Variable pass provides any additional benefit of resilience?
I believe the design is strong and do not recommend changing the tracking pass status. This is only useful for debugging reasons.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I kept this as-is. The fail is done if the control has drifted too much in time from where it should have happened.

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;
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);
Expand All @@ -206,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);
Expand All @@ -231,23 +259,49 @@ 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

% 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)
marc-hb marked this conversation as resolved.
Show resolved Hide resolved
%% Reference AES 17 3.12.3
level_ms = mean(x .^ 2);
dbfs = 10 * log10(level_ms + 1e-20) + 20 * log10(sqrt(2));
end