mirror of https://github.com/thesofproject/sof.git
508 lines
15 KiB
Matlab
508 lines
15 KiB
Matlab
function gui()
|
|
pkg load signal;
|
|
clear;
|
|
EQ = [];
|
|
Fs = get_config().Fs;
|
|
nyquist_f = Fs/2;
|
|
fn = 1;
|
|
% create figure and panel on it
|
|
fn = figure(fn, "toolbar", "none", "menubar", "none", 'Position', [200 200 1500 700]); fn = fn + 1;
|
|
EQ_right = uipanel ("title", "EQ Right", "position", [0, 0, 1, 0.5]);
|
|
EQ_left = uipanel ("title", "EQ Left", "position", [0, 0.5, 1, 0.5]);
|
|
main_panes = [struct("pane", EQ_left); struct("pane", EQ_right)];
|
|
|
|
% UI panel relative position table
|
|
positions = [
|
|
[0 0 0.25 0.5 ]
|
|
[0.25 0 0.5 0.5 ]
|
|
[0.5 0 0.75 0.5 ]
|
|
[0.75 0 1 0.5 ]
|
|
[0 0.5 0.25 1 ]
|
|
[0.25 0.5 0.5 1 ]
|
|
[0.5 0.5 0.75 1 ]
|
|
[0.75 0.5 1 1 ]
|
|
];
|
|
|
|
fn = figure(fn); fn = fn + 1;
|
|
h = [0 0];
|
|
w = [1 nyquist_f];
|
|
n_octaves = 10;
|
|
subplot(2, 1, 1);
|
|
main_panes(1).plot = plot(log2(w), h, "linewidth", 4);
|
|
% Range set to match http://audio-tuning.appspot.com/
|
|
axis([log2(nyquist_f / (2 ^ n_octaves)), log2(nyquist_f), -24, 18]);
|
|
grid on;
|
|
subplot(2, 1, 2);
|
|
main_panes(2).plot = plot(log2(w), h, "linewidth", 4);
|
|
% Range set to match http://audio-tuning.appspot.com/
|
|
axis([log2(nyquist_f / (2 ^ n_octaves)), log2(nyquist_f), -24, 18]);
|
|
grid on;
|
|
% TODO add ability to switch between octaves and log(Hz)
|
|
for c = 1:2
|
|
for i = 1:length(positions)
|
|
EQ = initialize_biquad(i, c, positions(i, 1:end), main_panes, Fs, EQ);
|
|
endfor
|
|
endfor
|
|
% octave is silly and doesn't support references so we have to re-write the
|
|
% control matrix so the callbacks have access without using globals
|
|
for c = 1:2
|
|
for i = 1:length(positions)
|
|
update_control_elements(EQ(i, c), EQ);
|
|
endfor
|
|
endfor
|
|
fn = figure(fn, "toolbar", "none", "menubar", "none", 'Position', [200 200 300 220]); fn = fn + 1;
|
|
menu_window = struct();
|
|
menu_window.ip_box = uicontrol( ...
|
|
"style", "edit", ...
|
|
"position", [120 190 180 30]);
|
|
menu_window.ip_label = uicontrol( ...
|
|
"style", "text",
|
|
"string", "DUT IP:", ...
|
|
"position", [0 190 120 30]);
|
|
menu_window.user_box = uicontrol( ...
|
|
"style", "edit", ...
|
|
"position", [120 160 180 30]);
|
|
menu_window.user_label = uicontrol( ...
|
|
"style", "text",
|
|
"string", "DUT User:", ...
|
|
"position", [0 160 120 30]);
|
|
menu_window.device_box = uicontrol( ...
|
|
"style", "edit", ...
|
|
"position", [120 130 180 30]);
|
|
menu_window.device_label = uicontrol( ...
|
|
"style", "text",
|
|
"string", "Device Num:", ...
|
|
"position", [0 130 120 30]);
|
|
menu_window.alsa_box = uicontrol( ...
|
|
"style", "edit", ...
|
|
"position", [120 100 180 30]);
|
|
menu_window.alsa_label = uicontrol( ...
|
|
"style", "text",
|
|
"string", "ALSA Ctl Num:", ...
|
|
"position", [0 100 120 30]);
|
|
menu_window.upload = uicontrol( ...
|
|
"style", "pushbutton", ...
|
|
'string', 'Push config to DUT', ...
|
|
"callback", @push_config, ...
|
|
"position", [0 50 300 50], ...
|
|
"userdata", struct("menu_window", menu_window, "EQ", EQ));
|
|
menu_window.load_dsp = uicontrol( ...
|
|
"style", "pushbutton", ...
|
|
'string', 'Load dsp.ini', ...
|
|
"callback", @eq_load_dsp_ini, ...
|
|
"position", [0 0 300 50], ...
|
|
"userdata", struct("EQ", EQ));
|
|
end
|
|
|
|
function conf = get_config()
|
|
% since octave has no definitive way to have constants we are going to hack a
|
|
% struct into behaving like one
|
|
conf = struct( ...
|
|
"Fs", 48000 ...
|
|
);
|
|
end
|
|
|
|
function set_udata_field(h, field, value)
|
|
udata = get(h, "userdata");
|
|
udata = setfield(udata, field, value);
|
|
set(h, "userdata", udata);
|
|
end
|
|
|
|
function peq = build_peq(channel, EQ)
|
|
peq = [];
|
|
for i = 1:8
|
|
biquad = EQ(i, channel);
|
|
if get(biquad.enable_switch, "value") == 1
|
|
type = get(biquad.type.dropdown, "value");
|
|
freq = str2num(get(biquad.freq.edit, "string"));
|
|
Q = str2double(get(biquad.Q.edit, "string"));
|
|
gain = str2double(get(biquad.gain.edit, "string"));
|
|
% dropdown maps straight to filter index
|
|
% filter only uses needed arguments, all others will be ignored
|
|
peq = [peq; type, freq, gain, Q];
|
|
endif
|
|
endfor
|
|
end
|
|
|
|
function update_plot(channel, main_panes, EQ)
|
|
Fs = get_config().Fs;
|
|
nyquist_f = Fs / 2;
|
|
pl = main_panes(channel).plot;
|
|
spectrum_size = 1000;
|
|
n = logspace(log10(1), log10(nyquist_f), spectrum_size);
|
|
peq = build_peq(channel, EQ);
|
|
[z p k] = eq_define_parametric_eq(peq, Fs);
|
|
% octave gets angry when you pile up poles and zeros
|
|
[num den] = zp2tf(z, p, k);
|
|
[h, w] = freqz(num,den, n, Fs);
|
|
h = 20*log10(h);
|
|
set(pl, "YData", h, "XData", log2(w));
|
|
end
|
|
|
|
function set_section_visibility(section, visible)
|
|
for [control, control_name] = section
|
|
set(control, "visible", visible);
|
|
endfor
|
|
end
|
|
|
|
function dropdown_callback(h, event)
|
|
udata = get(h, "userdata");
|
|
EQ = udata.EQ;
|
|
biquad = EQ(udata.index, udata.channel);
|
|
eq = eq_defaults();
|
|
switch (get(h, "value"))
|
|
case eq.PEQ_HP1
|
|
% highpass 1st order
|
|
set_section_visibility(biquad.Q, "off");
|
|
set_section_visibility(biquad.gain, "off");
|
|
case eq.PEQ_HP2
|
|
% highpass 2nd order
|
|
set_section_visibility(biquad.Q, "off");
|
|
set_section_visibility(biquad.gain, "off");
|
|
case eq.PEQ_LP1
|
|
% lowpass 1st order
|
|
set_section_visibility(biquad.Q, "off");
|
|
set_section_visibility(biquad.gain, "off");
|
|
case eq.PEQ_LP2
|
|
% lowpass 2nd order
|
|
set_section_visibility(biquad.Q, "off");
|
|
set_section_visibility(biquad.gain, "off");
|
|
case eq.PEQ_LS1
|
|
% lowshelf 1st order
|
|
set_section_visibility(biquad.Q, "off");
|
|
set_section_visibility(biquad.gain, "on");
|
|
case eq.PEQ_LS2
|
|
% lowhelf 2nd order
|
|
set_section_visibility(biquad.Q, "off");
|
|
set_section_visibility(biquad.gain, "on");
|
|
case eq.PEQ_HS1
|
|
% highshelf 1st order
|
|
set_section_visibility(biquad.Q, "off");
|
|
set_section_visibility(biquad.gain, "on");
|
|
case eq.PEQ_HS2
|
|
% highshelf 2nd order
|
|
set_section_visibility(biquad.Q, "off");
|
|
set_section_visibility(biquad.gain, "on");
|
|
case eq.PEQ_PN2
|
|
% peaking
|
|
set_section_visibility(biquad.Q, "on");
|
|
set_section_visibility(biquad.gain, "on");
|
|
case eq.PEQ_LP4
|
|
% lowpass 4th order
|
|
set_section_visibility(biquad.Q, "off");
|
|
set_section_visibility(biquad.gain, "off");
|
|
case eq.PEQ_HP4
|
|
% highpass 4th order
|
|
set_section_visibility(biquad.Q, "off");
|
|
set_section_visibility(biquad.gain, "off");
|
|
case eq.PEQ_LP2G
|
|
% lowpass 2nd order with resonnance
|
|
set_section_visibility(biquad.Q, "on");
|
|
set_section_visibility(biquad.gain, "off");
|
|
case eq.PEQ_HP2G
|
|
% highpass 2nd order with resonnance
|
|
set_section_visibility(biquad.Q, "on");
|
|
set_section_visibility(biquad.gain, "off");
|
|
case eq.PEQ_BP2
|
|
% bandpass
|
|
set_section_visibility(biquad.Q, "on");
|
|
set_section_visibility(biquad.gain, "off");
|
|
case eq.PEQ_NC2
|
|
% notch
|
|
set_section_visibility(biquad.Q, "on");
|
|
set_section_visibility(biquad.gain, "off");
|
|
case eq.PEQ_LS2G
|
|
% lowshelf, CRAS implementation
|
|
set_section_visibility(biquad.Q, "off");
|
|
set_section_visibility(biquad.gain, "on");
|
|
case eq.PEQ_HS2G
|
|
% highshelf, CRAS implementation
|
|
set_section_visibility(biquad.Q, "off");
|
|
set_section_visibility(biquad.gain, "on");
|
|
endswitch
|
|
update_plot(udata.channel, udata.main_panes, EQ);
|
|
end
|
|
|
|
function enable_callback(h, event)
|
|
off = get(h, "value") == 0;
|
|
if off
|
|
visible = "off";
|
|
else
|
|
visible = "on";
|
|
endif
|
|
udata = get(h, "userdata");
|
|
EQ = udata.EQ;
|
|
biquad = EQ(udata.index, udata.channel);
|
|
for [section, section_name] = biquad
|
|
if strcmp(section_name, "enable_switch") || strcmp(section_name, "pane")
|
|
continue
|
|
endif
|
|
set_section_visibility(section, visible);
|
|
endfor
|
|
if !off
|
|
dropdown_callback(biquad.type.dropdown,0);
|
|
endif
|
|
update_plot(udata.channel, udata.main_panes, EQ);
|
|
end
|
|
|
|
function slider_callback(h, event)
|
|
udata = get(h, "userdata");
|
|
EQ = udata.EQ;
|
|
editbox = getfield(EQ(udata.index, udata.channel), udata.prefix).edit;
|
|
val = get(h, "value");
|
|
if udata.is_log
|
|
val = 0.1*2^val;
|
|
endif
|
|
set(editbox, "string", num2str(val));
|
|
update_plot(udata.channel, udata.main_panes, EQ);
|
|
end
|
|
|
|
function edit_callback(h, event)
|
|
udata = get(h, "userdata");
|
|
EQ = udata.EQ;
|
|
slider = getfield(EQ(udata.index, udata.channel), udata.prefix).slider;
|
|
val = str2num(get(h, "string"));
|
|
if udata.is_log && val != 0
|
|
val = log2(val/0.1);
|
|
end
|
|
set(slider, "value", val);
|
|
update_plot(udata.channel, udata.main_panes, EQ);
|
|
end
|
|
|
|
function EQ = initialize_biquad(index, channel, position, main_panes, Fs, EQ)
|
|
nyquist_f = Fs / 2;
|
|
% this string matches the order defined in eq.PEQ_*, do not change the order
|
|
eq_strings = {"highpass 1st order", "highpass 2nd order", ...
|
|
"lowpass 1st order", "lowpass 2nd order", "lowshelf 1st order", ...
|
|
"lowshelf 2nd order", "highshelf 1st order", "highshelf 2nd order", ...
|
|
"peaking 2nd order", "lowpass 4th order", "highpass 4th order", ...
|
|
"lowpass 2nd order (Google)", "highpass 2nd order (Google)", ...
|
|
"bandpass 2nd order", "notch 2nd order", "lowshelf 2nd order (Google)", ...
|
|
"highshelf 2nd order (Google)"};
|
|
eq = eq_defaults();
|
|
% UI panels, we have to use absolute positioning
|
|
%
|
|
% +-------+-----------+-------+--+--+--+--+--+----------+
|
|
% | Label | Dropdown | | | | | | | checkbox |
|
|
% +-------+-----------+-------+--+--+--+--+--+----------+
|
|
% | Label | input box | label | slider |
|
|
% +-------+-----------+-------+-------------------------+
|
|
% | Label | input box | label | slider |
|
|
% +-------+-----------+-------+-------------------------+
|
|
% | Label | input box | label | slider |
|
|
% +-------+-----------+-------+-------------------------+
|
|
|
|
EQ(index, channel).pane = uipanel ("parent", main_panes(channel).pane, "position", position);
|
|
p = EQ(index, channel).pane;
|
|
% octave is silly and doesn't let one position controls relatively inside a uipanel
|
|
EQ(index, channel).gain.label = uicontrol( ...
|
|
"style", "text", ...
|
|
"parent", p, ...
|
|
"position", [0 10 40 30], ...
|
|
"visible", "off", ...
|
|
"string", "Gain");
|
|
EQ(index, channel).gain.edit = uicontrol( ...
|
|
"style", "edit", ...
|
|
"parent", p, ...
|
|
"position", [40 10 90 30], ...
|
|
"string", "0", ...
|
|
"userdata", struct( ...
|
|
"index", index, ...
|
|
"channel", channel, ...
|
|
"prefix", "gain", ...
|
|
"is_log", false, ...
|
|
"EQ", EQ, ...
|
|
"main_panes", main_panes), ...
|
|
"visible", "off", ...
|
|
"callback", @edit_callback);
|
|
EQ(index, channel).gain.units = uicontrol( ...
|
|
"style", "text", ...
|
|
"parent", p, ...
|
|
"string", "dB", ...
|
|
"visible", "off", ...
|
|
"position", [140 10 17 30]);
|
|
EQ(index, channel).gain.slider = uicontrol( ...
|
|
"style", "slider", ...
|
|
"parent", p, ...
|
|
"position", [160 10 200 30], ...
|
|
"min", -40, ...
|
|
"max", 40, ...
|
|
"userdata", struct(
|
|
"index", index, ...
|
|
"channel", channel, ...
|
|
"prefix", "gain", ...
|
|
"is_log", false, ...
|
|
"EQ", EQ, ...
|
|
"main_panes", main_panes), ...
|
|
"visible", "off", ...
|
|
"callback", @slider_callback);
|
|
|
|
EQ(index, channel).Q.label = uicontrol( ...
|
|
"style", "text", ...
|
|
"parent", p, ...
|
|
"position", [0 50 50 30], ...
|
|
"visible", "off", ...
|
|
"string", "Q");
|
|
EQ(index, channel).Q.edit = uicontrol( ...
|
|
"style", "edit", ...
|
|
"parent", p, ...
|
|
"position", [40 50 90 30], ...
|
|
"string", "1", ...
|
|
"userdata", struct( ...
|
|
"index", index, ...
|
|
"channel", channel, ...
|
|
"prefix", "Q", ...
|
|
"is_log", true, ...
|
|
"EQ", EQ, ...
|
|
"main_panes", main_panes), ...
|
|
"visible", "off", ...
|
|
"callback", @edit_callback);
|
|
EQ(index, channel).Q.slider = uicontrol( ...
|
|
"style", "slider", ...
|
|
"parent", p, ...
|
|
"position", [160 50 200 30], ...
|
|
"min", 0, ...
|
|
"max", log2(1000/0.1), ...
|
|
"value", log2(1/0.1), ...
|
|
"userdata", struct(
|
|
"index", index, ...
|
|
"channel", channel, ...
|
|
"prefix", "Q", ...
|
|
"is_log", true, ...
|
|
"EQ", EQ, ...
|
|
"main_panes", main_panes), ...
|
|
"visible", "off", ...
|
|
"callback", @slider_callback);
|
|
|
|
EQ(index, channel).freq.label = uicontrol( ...
|
|
"style", "text", ...
|
|
"parent", p, ...
|
|
"position", [0 90 40 30], ...
|
|
"visible", "off", ...
|
|
"string", "Freq");
|
|
EQ(index, channel).freq.edit = uicontrol( ...
|
|
"style", "edit", ...
|
|
"parent", p, ...
|
|
"position", [40 90 90 30], ...
|
|
"string", "350", ...
|
|
"userdata", struct( ...
|
|
"index", index, ...
|
|
"channel", channel, ...
|
|
"prefix", "freq", ...
|
|
"is_log", true, ...
|
|
"EQ", EQ, ...
|
|
"main_panes", main_panes), ...
|
|
"visible", "off", ...
|
|
"callback", @edit_callback);
|
|
EQ(index, channel).freq.units = uicontrol( ...
|
|
"style", "text", ...
|
|
"parent", p, ...
|
|
"position", [140 90 17 30], ...
|
|
"visible", "off", ...
|
|
"string", "Hz");
|
|
EQ(index, channel).freq.slider = uicontrol( ...
|
|
"style", "slider", ...
|
|
"parent", p, ...
|
|
"position", [160 90 200 30], ...
|
|
"min", 1, ...
|
|
"max", log2(nyquist_f/0.1), ...
|
|
"value", log2(350/0.1), ...
|
|
"userdata", struct(
|
|
"index", index, ...
|
|
"channel", channel, ...
|
|
"prefix", "freq", ...
|
|
"is_log", true, ...
|
|
"EQ", EQ, ...,
|
|
"main_panes", main_panes), ...
|
|
"visible", "off", ...
|
|
"callback", @slider_callback);
|
|
|
|
EQ(index, channel).type.label = uicontrol( ...
|
|
"style", "text", ...
|
|
"parent", p, ...
|
|
"position", [0 130 40 30], ...
|
|
"visible", "off", ...
|
|
"string", "Type");
|
|
EQ(index, channel).type.dropdown = uicontrol( ...
|
|
"style", "popupmenu", ...
|
|
"parent", p, ...
|
|
"position", [40 130 240 30], ...
|
|
"string", eq_strings, ...
|
|
"value", eq.PEQ_PN2, ...
|
|
"userdata", struct( ...
|
|
"index", index, ...
|
|
"channel", channel, ...
|
|
"EQ", EQ, ...
|
|
"main_panes", main_panes), ...
|
|
"visible", "off", ...
|
|
"callback", @dropdown_callback);
|
|
EQ(index, channel).enable_switch = uicontrol( ...
|
|
"style", "checkbox", ...
|
|
"parent", p, ...
|
|
"position", [350 130 30 30], ...
|
|
"value", 0, ...
|
|
"userdata", struct(
|
|
"index", index, ...
|
|
"channel", channel, ...
|
|
"EQ", EQ, ...
|
|
"main_panes", main_panes), ...
|
|
"callback", @enable_callback);
|
|
end
|
|
|
|
function push_config(h, evnt)
|
|
udata = get(h, "userdata");
|
|
window = udata.menu_window;
|
|
EQ = udata.EQ;
|
|
ip = get(window.ip_box, "string");
|
|
user = get(window.user_box, "string");
|
|
device = str2num(get(window.device_box, "string"));
|
|
alsa_num = str2num(get(window.alsa_box, "string"));
|
|
target = struct("ip", ip, "user", user, "device", device, "control", alsa_num);
|
|
|
|
temp_file = tempname();
|
|
|
|
eq_left = eq_defaults();
|
|
eq_left.enable_iir = 1;
|
|
eq_left.peq = build_peq(1, EQ);
|
|
eq_left.norm_type = 'peak';
|
|
eq_left.norm_offs_db = 0;
|
|
eq_left.fs = get_config().Fs;
|
|
|
|
eq_right = eq_defaults();
|
|
eq_right.enable_iir = 1;
|
|
eq_right.peq = build_peq(2, EQ);
|
|
eq_right.norm_type = 'peak';
|
|
eq_right.norm_offs_db = 0;
|
|
eq_right.fs = get_config().Fs;
|
|
|
|
eq_left = eq_compute(eq_left);
|
|
eq_right = eq_compute(eq_right);
|
|
|
|
bq_left = eq_iir_blob_quant(eq_left.p_z, eq_left.p_p, eq_left.p_k);
|
|
bq_right = eq_iir_blob_quant(eq_right.p_z, eq_right.p_p, eq_right.p_k);
|
|
|
|
channels_in_config = 2;
|
|
assign_response = [0 1];
|
|
num_responses = 2;
|
|
bm = eq_iir_blob_merge(channels_in_config, ...
|
|
num_responses, ...
|
|
assign_response, ...
|
|
[bq_left bq_right]);
|
|
|
|
bp = eq_iir_blob_pack(bm);
|
|
eq_alsactl_write(temp_file, bp);
|
|
eq_deploy_to_dut(target, temp_file);
|
|
end
|
|
|
|
function update_control_elements(section, EQ)
|
|
set_udata_field(section.gain.edit, "EQ", EQ);
|
|
set_udata_field(section.gain.slider, "EQ", EQ);
|
|
set_udata_field(section.Q.edit, "EQ", EQ);
|
|
set_udata_field(section.Q.slider, "EQ", EQ);
|
|
set_udata_field(section.freq.edit, "EQ", EQ);
|
|
set_udata_field(section.freq.slider, "EQ", EQ);
|
|
set_udata_field(section.type.dropdown, "EQ", EQ);
|
|
set_udata_field(section.enable_switch, "EQ", EQ);
|
|
end
|
|
|