sof/tools/tune/eq/gui.m

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