Skip to content

Commit 72a7b24

Browse files
committed
audio: eq_iir: tune: add sof_mls_freq_resp_local.sh
Add a bash driver that turns the older manually-edited mls_play_config.txt / mls_rec_config.txt workflow into a one-shot "detect + configure + optionally measure" script for the local host. All artifacts (configs, MLS reference, per-channel captures, CSV, PNG plot) land under a per-run data subdirectory so the tune directory does not fill up with per-machine files. aplay -l and arecord -l are parsed to enumerate PCM devices. Speaker preference is: descriptor named "Speaker" first (SOF SDW cards advertise literal endpoint names), then SOF+Analog, then any Analog, then the first non-HDMI/DP/Deepbuffer device. Mic preference is a known calibrated measurement mic (UMM-6, UMIK, MiniDSP, EMM-6, ECM8000) first, then any non-webcam USB card, then SOF DMIC, then analog. --speaker / --mic override both. The auto id is sanitize(DMI vendor + product) plus a mic tag, and gets a -2/-3/... suffix when a prior run's outputs are already in the data dir so re-measuring at a new mic position never clobbers old captures. Capture format and channel count are probed with arecord --dump-hw-params. S32_LE is preferred over S16_LE so the SOF DMIC PCM (which only exposes S32_LE) works out of the box, and the default channel count is the mic's advertised maximum. Optional --cal PATH threads a two-column [freq_hz, mag_db] microphone calibration file into rec.cal for sof_mls_freq_resp.m. Opt-in --prep loads IPC4 pass-through blobs into the speaker card's IIR/FIR bytes controls and turns DRC/MBDRC/TDFB switches off. Both amixer and sof-ctl are invoked via the iface=MIXER,name='...' / -c name='...' forms because numid-based access misreports max sizes and trips a snd_ctl_elem_info() assertion on current alsa-utils. Every external ioctl is wrapped in timeout(1), and each bytes control's actual max size is probed before writing so 1-byte topology stubs get skipped instead of crashing sof-ctl. sof_mls_freq_resp.m gains three optional arguments (play_config_fn, rec_config_fn, data_dir), a `pkg load signal;` up front so distros where octave-signal is separate fail cleanly, and an mls-<id>.png plot next to the CSV. Existing callers using the old positional signature keep working via defaults. Signed-off-by: Seppo Ingalsuo <seppo.ingalsuo@linux.intel.com>
1 parent 844c208 commit 72a7b24

2 files changed

Lines changed: 972 additions & 20 deletions

File tree

src/audio/eq_iir/tune/sof_mls_freq_resp.m

Lines changed: 89 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
1-
function [f, m_db] = sof_mls_freq_resp(id)
1+
function [f, m_db] = sof_mls_freq_resp(id, play_config_fn, rec_config_fn, data_dir)
22
%% Measure frequency response with MLS test signal
33
%
4-
% [f, m] = sof_mls_freq_resp(id)
4+
% [f, m] = sof_mls_freq_resp(id, play_config_fn, rec_config_fn, data_dir)
55
%
66
% Input parameters
7-
% id - A string identifier for test case. An id 'selftest' is for special
8-
% usage. It calculates response of filtered MLS signal and computes
9-
% the measurement vs. known. Deviation is reported as error. It can
10-
% be useful if the internal MLS measurement parameters are adjusted.
7+
% id - A string identifier for test case. An id 'selftest' is for
8+
% special usage. It calculates response of filtered MLS signal
9+
% and computes the measurement vs. known. Deviation is
10+
% reported as error. It can be useful if the internal MLS
11+
% measurement parameters are adjusted.
12+
% play_config_fn - Playback config file (default 'mls_play_config.txt').
13+
% rec_config_fn - Capture config file (default 'mls_rec_config.txt').
14+
% data_dir - Directory for the mls-<id>.{wav,txt} outputs and the
15+
% mls-ref.wav reference (default '.'). Created if missing.
1116
%
1217
% Output parameters
1318
% f - Frequency vector in Hz
@@ -17,7 +22,8 @@
1722
% mls_play_config.txt
1823
% mls_rec_config.txt
1924
%
20-
% The script will return also a text CSV format file with name mls-<id>.txt.
25+
% The script will also write a text CSV format file with name mls-<id>.txt
26+
% and a PNG plot of the aligned response as mls-<id>.png into data_dir.
2127
%
2228

2329
% SPDX-License-Identifier: BSD-3-Clause
@@ -26,6 +32,13 @@
2632
%
2733
% Author: Seppo Ingalsuo <seppo.ingalsuo@linux.intel.com>
2834

35+
%% Octave dependencies: mlsp12/sync_chirp and the resampling path pull in
36+
%% functions from the signal package. On most distros it ships separately
37+
%% from core Octave, so load it explicitly rather than fail cryptically.
38+
if exist('OCTAVE_VERSION', 'builtin')
39+
pkg load signal;
40+
end
41+
2942
%% Settings
3043
np = 1024; % Number of frequency points to use
3144
f_lo = 100; % Lower frequency limit for analysis
@@ -45,6 +58,16 @@
4558
if nargin < 1
4659
id = 'unknown';
4760
end
61+
if nargin < 2 || isempty(play_config_fn)
62+
play_config_fn = 'mls_play_config.txt';
63+
end
64+
if nargin < 3 || isempty(rec_config_fn)
65+
rec_config_fn = 'mls_rec_config.txt';
66+
end
67+
if nargin < 4 || isempty(data_dir)
68+
data_dir = '.';
69+
end
70+
ensure_dir(data_dir);
4871

4972
if strcmp(id, 'selftest')
5073
selftest = 1;
@@ -58,8 +81,8 @@
5881
else
5982
selftest = 0;
6083
end
61-
measfn = sprintf('mls-%s.wav', id);
62-
csvfn = sprintf('mls-%s.txt', id);
84+
measfn = fullfile(data_dir, sprintf('mls-%s.wav', id));
85+
csvfn = fullfile(data_dir, sprintf('mls-%s.txt', id));
6386

6487
%% Paths
6588
% Resolve the test_utils dir from the script location so the script works
@@ -74,7 +97,7 @@
7497
%% MLS
7598
n_mls = round(fs*t_mls_s);
7699
mls = 10^(a_mls_db/20) * (2 * mlsp12(1, n_mls) - 1);
77-
mlsfn = 'mls-ref.wav';
100+
mlsfn = fullfile(data_dir, 'mls-ref.wav');
78101
audiowrite(mlsfn, mls, fs);
79102

80103
%% Chip markers and parameters for find sync
@@ -101,8 +124,8 @@
101124
z(i2 + 1:end) = x2;
102125

103126
%% Get config
104-
rec_cfg = meas_remote_rec_config(fs, fmt);
105-
play_cfg = meas_remote_play_config;
127+
rec_cfg = meas_remote_rec_config(fs, fmt, rec_config_fn);
128+
play_cfg = meas_remote_play_config(play_config_fn);
106129

107130
%% Capture MLS from all playback channel at time
108131
mixfn = 'mlsmix.wav';
@@ -129,7 +152,16 @@
129152
else
130153
x = zeros(length(z), play_cfg.nch);
131154
x(:,i) = z;
132-
mixdfn = sprintf('%s/%s', tmp_dir, mixfn);
155+
% For an ssh play target we stage the WAV under tmp_dir and let
156+
% copy_playback scp it over to play_cfg.dir. For a local play
157+
% target copy_playback is a no-op, so write the WAV directly
158+
% into play_cfg.dir where remote_play (which is local aplay in
159+
% that case) will look for it.
160+
if play_cfg.ssh
161+
mixdfn = fullfile(tmp_dir, mixfn);
162+
else
163+
mixdfn = fullfile(play_cfg.dir, mixfn);
164+
end
133165
audiowrite(mixdfn, x, fs, 'BitsPerSample', bits);
134166
copy_playback(mixdfn, play_cfg);
135167
tcap = floor(6 + t_mls_s); % Capture for MLS +6s
@@ -159,7 +191,7 @@
159191
return
160192
else
161193
% nt is the sample count of the test signal, so the range
162-
% highlighted here must is r(d:d+nt-1, :).
194+
% highlighted here must be r(d:d+nt-1, :).
163195
si = d:d + nt - 1;
164196
hold on
165197
plot(ts(si), r(si, 1), 'g');
@@ -198,7 +230,7 @@
198230

199231
[f, m_db] = apply_mic_calibration(f, m_db, rec_cfg);
200232

201-
figure
233+
main_fig = figure;
202234
idx = find(f > f_align_hz, 1, 'first') - 1;
203235
if isempty(idx) || idx < 1
204236
error('No frequency bin at or below %.0f Hz for alignment', f_align_hz);
@@ -218,10 +250,28 @@
218250
hold on;
219251
plot(f, ref_db_align, 'r--');
220252
hold off;
253+
else
254+
% Interpreter 'none' so underscores in the id are drawn literally
255+
% instead of being taken as TeX subscript markers.
256+
title(sprintf('Measured frequency response: %s', id), ...
257+
'Interpreter', 'none');
221258
end
222259

223260
legend(labels);
224261

262+
%% Save the plot as PNG next to the CSV so the response is easy to share
263+
%% without pulling the raw capture through Octave again. print() picks
264+
%% whichever graphics toolkit is active (qt on desktop, gnuplot in
265+
%% --no-window-system runs), so both interactive and headless invocations
266+
%% produce a file.
267+
plot_fn = fullfile(data_dir, sprintf('mls-%s.png', id));
268+
try
269+
print(main_fig, plot_fn, '-dpng', '-r150');
270+
fprintf('Wrote %s\n', plot_fn);
271+
catch err
272+
warning('Could not save %s: %s', plot_fn, err.message);
273+
end
274+
225275
if selftest
226276
idx = find(f < f_hi);
227277
idx = find(f(idx) > f_lo);
@@ -326,22 +376,41 @@ function run_shell(cmd)
326376
q = [sq, strrep(s, sq, esc), sq];
327377
end
328378

329-
function play = meas_remote_play_config()
330-
play = get_config('mls_play_config.txt', 'play');
379+
function ensure_dir(d)
380+
if ~exist(d, 'dir')
381+
[ok, msg] = mkdir(d);
382+
if ~ok
383+
error('mkdir %s failed: %s', d, msg);
384+
end
385+
end
386+
end
387+
388+
function play = meas_remote_play_config(fn)
389+
play = get_config(fn, 'play');
331390
fprintf('\nThe settings for remote playback are\n');
391+
fprintf('Config : %s\n', fn);
332392
fprintf('Use ssh : %d\n', play.ssh);
333393
fprintf('User : %s\n', play.user);
334394
fprintf('Directory : %s\n', play.dir);
335395
fprintf('Device : %s\n', play.dev);
336396
fprintf('Channels : %d\n', play.nch);
337397
end
338398

339-
function rec = meas_remote_rec_config(fs, fmt)
340-
rec = get_config('mls_rec_config.txt', 'rec');
399+
function rec = meas_remote_rec_config(fs, fmt, fn)
400+
rec = get_config(fn, 'rec');
401+
% Let the config file override the ALSA format: some capture
402+
% devices (e.g. SOF DMIC) only expose S32_LE, so the shell driver
403+
% probes --dump-hw-params and writes rec.fmt_name / rec.bits in.
404+
if isfield(rec, 'fmt_name') && ~isempty(rec.fmt_name)
405+
fmt_use = rec.fmt_name;
406+
else
407+
fmt_use = fmt;
408+
end
341409
rec.fmt = sprintf('-t wav -c %d -f %s -r %d', ...
342-
rec.nch, fmt, fs);
410+
rec.nch, fmt_use, fs);
343411

344412
fprintf('\nThe settings for remote capture are\n');
413+
fprintf('Config : %s\n', fn);
345414
fprintf('Use ssh : %d\n', rec.ssh);
346415
fprintf('User : %s\n', rec.user);
347416
fprintf('Directory : %s\n', rec.dir);

0 commit comments

Comments
 (0)