Skip to content

Commit 12937b0

Browse files
committed
Audio: Sound Dose: Add Octave script to export A-weight filter
This patch adds script sof_sound_dose_time_domain_filters.m that exports IIR and FIR coefficients to approximate A-weight function. The current choice is IIR only for lower MCPS load. The script sof_sound_dose_blobs.m creates a few control blobs to test the sound_dose component. A simple script sof_sound_dose_ref.m to compute dBFS and MEL for a wav file is added to compare with firmware reported values. Signed-off-by: Seppo Ingalsuo <seppo.ingalsuo@linux.intel.com>
1 parent 5508077 commit 12937b0

3 files changed

Lines changed: 348 additions & 0 deletions

File tree

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
% Export configuration blobs for Sound Dose
2+
3+
% SPDX-License-Identifier: BSD-3-Clause
4+
%
5+
% Copyright (c) 2025, Intel Corporation.
6+
7+
function sof_sound_dose_blobs()
8+
9+
% Common definitions
10+
sof_tools = '../../../../tools';
11+
sof_tplg = fullfile(sof_tools, 'topology');
12+
fn.tpath = fullfile(sof_tplg, 'topology2/include/components/sound_dose');
13+
str_comp = "sound_dose_config";
14+
str_comment = "Exported with script sof_sound_dose_blobs.m";
15+
str_howto = "cd tools/tune/sound_dose; octave sof_sound_dose_blobs.m";
16+
17+
% Set the parameters here
18+
sof_tools = '../../../../tools';
19+
sof_tplg = fullfile(sof_tools, 'topology');
20+
sof_tplg_sound_dose = fullfile(sof_tplg, 'topology2/include/components/sound_dose');
21+
sof_ctl_sound_dose = fullfile(sof_tools, 'ctl/ipc4/sound_dose');
22+
23+
sof_sound_dose_paths(true);
24+
25+
blob8 = sof_sound_dose_build_blob([10000 0], 0);
26+
tplg2_fn = sprintf("%s/setup_sens_100db.conf", sof_tplg_sound_dose);
27+
sof_tplg2_write(tplg2_fn, blob8, str_comp, str_comment, str_howto);
28+
sof_alsactl_write([sof_ctl_sound_dose "/setup_sens_100db.txt"], blob8);
29+
30+
blob8 = sof_sound_dose_build_blob([0 0], 0);
31+
tplg2_fn = sprintf("%s/setup_sens_0db.conf", sof_tplg_sound_dose);
32+
sof_tplg2_write(tplg2_fn, blob8, str_comp, str_comment, str_howto);
33+
sof_alsactl_write([sof_ctl_sound_dose "/setup_sens_0db.txt"], blob8);
34+
35+
blob8 = sof_sound_dose_build_blob([0 0], 1);
36+
tplg2_fn = sprintf("%s/setup_vol_0db.conf", sof_tplg_sound_dose);
37+
sof_tplg2_write(tplg2_fn, blob8, str_comp, str_comment, str_howto);
38+
sof_alsactl_write([sof_ctl_sound_dose "/setup_vol_0db.txt"], blob8);
39+
40+
blob8 = sof_sound_dose_build_blob([-1000 0], 1);
41+
tplg2_fn = sprintf("%s/setup_vol_-10db.conf", sof_tplg_sound_dose);
42+
sof_tplg2_write(tplg2_fn, blob8, str_comp, str_comment, str_howto);
43+
sof_alsactl_write([sof_ctl_sound_dose "/setup_vol_-10db.txt"], blob8);
44+
45+
blob8 = sof_sound_dose_build_blob([-1000 0], 2);
46+
tplg2_fn = sprintf("%s/setup_gain_-10db.conf", sof_tplg_sound_dose);
47+
sof_tplg2_write(tplg2_fn, blob8, str_comp, str_comment, str_howto);
48+
sof_alsactl_write([sof_ctl_sound_dose "/setup_gain_-10db.txt"], blob8);
49+
50+
blob8 = sof_sound_dose_build_blob([0 0], 2);
51+
tplg2_fn = sprintf("%s/setup_gain_0db.conf", sof_tplg_sound_dose);
52+
sof_tplg2_write(tplg2_fn, blob8, str_comp, str_comment, str_howto);
53+
sof_alsactl_write([sof_ctl_sound_dose "/setup_gain_0db.txt"], blob8);
54+
55+
sof_sound_dose_paths(false);
56+
end
57+
58+
function sof_sound_dose_paths(enable)
59+
60+
common = '../../../../tools/tune/common';
61+
if enable
62+
addpath(common);
63+
else
64+
rmpath(common);
65+
end
66+
end
67+
68+
function blob8 = sof_sound_dose_build_blob(param_values, blob_param_id)
69+
70+
blob_type = 0;
71+
data_length = length(param_values);
72+
data_size = 2 * data_length;
73+
ipc_ver = 4;
74+
[abi_bytes, abi_size] = sof_get_abi(data_size, ipc_ver, blob_type, blob_param_id);
75+
blob_size = data_size + abi_size;
76+
blob8 = uint8(zeros(1, blob_size));
77+
blob8(1:abi_size) = abi_bytes;
78+
j = abi_size + 1;
79+
for i = 1:data_length
80+
blob8(j:j+1) = short2byte(int16(param_values(i)));
81+
j=j+2;
82+
end
83+
end
84+
85+
function bytes = int2byte(word)
86+
sh = [0 -8 -16 -24];
87+
bytes = uint8(zeros(1,4));
88+
bytes(1) = bitand(bitshift(word, sh(1)), 255);
89+
bytes(2) = bitand(bitshift(word, sh(2)), 255);
90+
bytes(3) = bitand(bitshift(word, sh(3)), 255);
91+
bytes(4) = bitand(bitshift(word, sh(4)), 255);
92+
end
93+
94+
function bytes = short2byte(word)
95+
sh = [0 -8];
96+
bytes = uint8(zeros(1,2));
97+
bytes(1) = bitand(bitshift(word, sh(1)), 255);
98+
bytes(2) = bitand(bitshift(word, sh(2)), 255);
99+
end
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
% Compute MEL every 1s %
2+
tc = 1.0;
3+
4+
% Load sound clip
5+
sound_clip = "/usr/share/sounds/alsa/Front_Right.wav";
6+
[x1, fs] = audioread(sound_clip);
7+
if size(x1, 2) == 1
8+
x1 = repmat(x1, 1, 2);
9+
end
10+
sx = size(x1);
11+
num_frames = sx(1);
12+
num_channels = sx(2);
13+
14+
% load A-weight filters
15+
load sof_sound_dose_time_domain_filters.mat
16+
17+
% Filter with IIR, FIR
18+
x2 = filter(b_iir, a_iir, x1);
19+
x3 = filter(b_fir, 1, x2);
20+
21+
%figure
22+
%plot(x1)
23+
24+
%figure
25+
%plot(x3)
26+
27+
% compute RMS level in 1s chunks
28+
np = 1;
29+
nc = tc * fs;
30+
num_chunks = floor(num_frames/nc);
31+
l_dbfs_all = zeros(num_chunks, num_channels);
32+
mel_all = zeros(num_chunks, 1);
33+
34+
if 1
35+
36+
dbfs_offs = 3.01;
37+
dbfs_offs_weight_filters = 3;
38+
i1 = 1;
39+
for n = 1:num_chunks
40+
sum = 0;
41+
i2 = i1 + nc - 1;
42+
for ch = 1:num_channels
43+
y = x3(i1:i2, ch);
44+
sum = sum + mean(y.^2);
45+
l_dbfs = 20*log10(sqrt(mean(y.^2))) + dbfs_offs + dbfs_offs_weight_filters;
46+
l_dbfs_all(np, ch) = l_dbfs;
47+
end
48+
mel = 10*log10(sum) + dbfs_offs_weight_filters;
49+
if num_channels > 1
50+
mel = mel - 1.5 * num_channels;
51+
end
52+
mel_all(np) = mel;
53+
i1 = i1 + nc;
54+
np = np + 1;
55+
end
56+
57+
58+
else
59+
60+
dbfs_coef = 10 / log2(10);
61+
mean_offs = log2(1/nc);
62+
dbfs_offs = 3.01;
63+
dbfs_offs_weight_filters = 3;
64+
i1 = 1;
65+
for n = 1:num_chunks
66+
i2 = i1 + nc - 1;
67+
for ch = 1:num_channels
68+
y = x3(i1:i2, ch);
69+
%y = ones(nc, 1)*10000/32768;
70+
%size(y)
71+
% RMS level
72+
%l_dbfs = 20*log10(sqrt(mean(y.^2))) + 3.01 + dbfs_offs_weight_filters;
73+
%l_dbfs = 10*log10(mean(y.^2)) + 3.01 + dbfs_offs_weight_filters;
74+
%l_dbfs = 10*log10(sum(y.^2)) + dbfs_offs + dbfs_offs_weight_filters;
75+
log2_squares_sum = log2(sum(y.^2));
76+
log2_mean = log2_squares_sum + mean_offs;
77+
l_dbfs = dbfs_coef * log2_mean + dbfs_offs + dbfs_offs_weight_filters;
78+
l_dbfs_all(np, ch) = l_dbfs;
79+
end
80+
mel_all(np) = sum(l_dbfs_all(np,:)) - 3;
81+
i1 = i1 + nc;
82+
np = np + 1;
83+
end
84+
85+
end
86+
87+
figure(1)
88+
plot(l_dbfs_all)
89+
grid on
90+
xlabel('Time (s)');
91+
ylabel('Level (dBSFS)');
92+
93+
figure(2)
94+
plot(mel_all)
95+
grid on
96+
xlabel('Time (s)');
97+
ylabel('MEL (dB)');
98+
99+
mel_all
100+
101+
mel_all_rnd = round(mel_all)
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
% Export time domain IIR and FIR filter to apply A-weight for sound dose.
2+
%
3+
% Usage:
4+
% sof_sound_dose_time_domain_filters()
5+
6+
% SPDX-License-Identifier: BSD-3-Clause
7+
%
8+
% Copyright (c) 2025, Intel Corporation. All rights reserved.
9+
10+
function sof_sound_dose_time_domain_filters()
11+
12+
sof_sound_dose = '../../sound_dose';
13+
sof_ctl = '../../../../tools/ctl';
14+
sound_dose_paths(true);
15+
16+
17+
prm.fir_coef_fn = fullfile(sof_sound_dose, 'sound_dose_fir_48k.h');
18+
prm.iir_coef_fn = fullfile(sof_sound_dose, 'sound_dose_iir_48k.h');
19+
prm.fir_blob_fn = fullfile(sof_ctl, 'ipc4/eq_fir/sound_dose_fir_48k.txt');
20+
prm.iir_blob_fn = fullfile(sof_ctl, 'ipc4/eq_iir/sound_dose_iir_48k.txt');
21+
prm.fir_blob_vn = 'sound_dose_fir_48k';
22+
prm.iir_blob_vn = 'sound_dose_iir_48k';
23+
prm.ipc_ver = 4;
24+
eq = sound_dose_filters(48e3);
25+
export_filters(eq, prm);
26+
27+
sound_dose_paths(false);
28+
end
29+
30+
function export_filters(eq, prm)
31+
32+
b_fir = 1;
33+
b_iir = 1;
34+
a_iir = 1;
35+
36+
% Export FIR
37+
if eq.enable_fir
38+
bq = sof_eq_fir_blob_quant(eq.b_fir);
39+
channels_in_config = 2; % Setup max 2 channels EQ
40+
assign_response = [0 0]; % Same response for L and R
41+
num_responses = 1; % One response
42+
bm = sof_eq_fir_blob_merge(channels_in_config, ...
43+
num_responses, ...
44+
assign_response, ...
45+
bq);
46+
bp = sof_eq_fir_blob_pack(bm, prm.ipc_ver);
47+
sof_export_c_eq_uint32t(prm.fir_coef_fn, bp, prm.fir_blob_vn, 0);
48+
sof_alsactl_write(prm.fir_blob_fn, bp);
49+
b_fir = eq.b_fir;
50+
end
51+
52+
% Export IIR
53+
if eq.enable_iir
54+
coefq = sof_eq_iir_blob_quant(eq.p_z, eq.p_p, eq.p_k);
55+
channels_in_config = 2; % Setup max 2 channels EQ
56+
assign_response = [0 0]; % Same response for L and R
57+
num_responses = 1; % One response
58+
coefm = sof_eq_iir_blob_merge(channels_in_config, ...
59+
num_responses, ...
60+
assign_response, ...
61+
coefq);
62+
coefp = sof_eq_iir_blob_pack(coefm, prm.ipc_ver);
63+
sof_export_c_eq_uint32t(prm.iir_coef_fn, coefp, prm.iir_blob_vn, 0);
64+
sof_alsactl_write(prm.iir_blob_fn, coefp);
65+
[b_iir, a_iir] = zp2tf( eq.p_z, eq.p_p, eq.p_k);
66+
end
67+
68+
% Export mat file
69+
save sof_sound_dose_time_domain_filters.mat b_fir b_iir a_iir;
70+
71+
end
72+
73+
function eq = sound_dose_filters(fs)
74+
75+
np = 200;
76+
f_hi = 0.95 * fs/2;
77+
fv = logspace(log10(10), log10(f_hi), np);
78+
ref_a_weight = func_a_weight_db(fv);
79+
80+
eq = sof_eq_defaults();
81+
eq.fs = fs;
82+
eq.enable_fir = 0;
83+
eq.enable_iir = 1;
84+
eq.iir_norm_type = '1k'; % At 1 kHz -3 dB
85+
eq.iir_norm_offs_db = -3;
86+
eq.fir_norm_type = '1k'; % At 1 kHz 0 dB, total -3 dB gain to avoid clip
87+
eq.fir_norm_offs_db = 0;
88+
eq.p_fmin = 10;
89+
eq.p_fmax = 30e3;
90+
91+
eq.fir_minph = 1;
92+
eq.fir_beta = 4;
93+
eq.fir_length = 31;
94+
eq.fir_autoband = 0;
95+
eq.fmin_fir = 300;
96+
eq.fmax_fir = f_hi;
97+
98+
eq.raw_f = fv;
99+
eq.raw_m_db = zeros(1, length(fv));
100+
eq.target_f = fv;
101+
eq.target_m_db = ref_a_weight;
102+
103+
eq.peq = [ ...
104+
eq.PEQ_HP1 12 0 0; ...
105+
eq.PEQ_HP1 20 0 0; ...
106+
eq.PEQ_HP2 280 0 0; ...
107+
eq.PEQ_PN2 245 -5.45 0.5; ...
108+
eq.PEQ_HS1 13000 -3 0; ...
109+
eq.PEQ_HS1 14000 -3 0; ...
110+
];
111+
112+
eq = sof_eq_compute(eq);
113+
sof_eq_plot(eq, 1);
114+
figure(3);
115+
axis([10 20e3 -60 10]);
116+
117+
118+
end
119+
120+
% See https://en.wikipedia.org/wiki/A-weighting
121+
% IEC 61672-1:2013 Electroacoustics - Sound level meters,
122+
% Part 1: Specifications. IEC. 2013.
123+
124+
function a_weight = func_a_weight_db(fv)
125+
a_weight = 20*log10(func_ra(fv)) - 20*log10(func_ra(1000));
126+
end
127+
128+
function y = func_ra(f)
129+
f2 = f.^2;
130+
f4 = f.^4;
131+
c1 = 12194^2;
132+
c2 = 20.6^2;
133+
c3 = 107.7^2;
134+
c4 = 737.9^2;
135+
c5 = 12194^2;
136+
y = c1 * f4 ./ ((f2 + c2).*sqrt((f2 + c3).*(f2 + c4).*(f2 + c5)));
137+
end
138+
139+
function sound_dose_paths(enable)
140+
switch enable
141+
case true
142+
addpath('../../eq_iir/tune');
143+
addpath('../../../../tools/tune/common');
144+
case false
145+
rmpath('../../eq_iir/tune');
146+
rmpath('../../../../tools/tune/common');
147+
end
148+
end

0 commit comments

Comments
 (0)