1function gui() 2 pkg load signal; 3 clear; 4 EQ = []; 5 Fs = get_config().Fs; 6 nyquist_f = Fs/2; 7 fn = 1; 8 % create figure and panel on it 9 fn = figure(fn, "toolbar", "none", "menubar", "none", 'Position', [200 200 1500 700]); fn = fn + 1; 10 EQ_right = uipanel ("title", "EQ Right", "position", [0, 0, 1, 0.5]); 11 EQ_left = uipanel ("title", "EQ Left", "position", [0, 0.5, 1, 0.5]); 12 main_panes = [struct("pane", EQ_left); struct("pane", EQ_right)]; 13 14 % UI panel relative position table 15 positions = [ 16 [0 0 0.25 0.5 ] 17 [0.25 0 0.5 0.5 ] 18 [0.5 0 0.75 0.5 ] 19 [0.75 0 1 0.5 ] 20 [0 0.5 0.25 1 ] 21 [0.25 0.5 0.5 1 ] 22 [0.5 0.5 0.75 1 ] 23 [0.75 0.5 1 1 ] 24 ]; 25 26 fn = figure(fn); fn = fn + 1; 27 h = [0 0]; 28 w = [1 nyquist_f]; 29 n_octaves = 10; 30 subplot(2, 1, 1); 31 main_panes(1).plot = plot(log2(w), h, "linewidth", 4); 32 % Range set to match http://audio-tuning.appspot.com/ 33 axis([log2(nyquist_f / (2 ^ n_octaves)), log2(nyquist_f), -24, 18]); 34 grid on; 35 subplot(2, 1, 2); 36 main_panes(2).plot = plot(log2(w), h, "linewidth", 4); 37 % Range set to match http://audio-tuning.appspot.com/ 38 axis([log2(nyquist_f / (2 ^ n_octaves)), log2(nyquist_f), -24, 18]); 39 grid on; 40 % TODO add ability to switch between octaves and log(Hz) 41 for c = 1:2 42 for i = 1:length(positions) 43 EQ = initialize_biquad(i, c, positions(i, 1:end), main_panes, Fs, EQ); 44 endfor 45 endfor 46 % octave is silly and doesn't support references so we have to re-write the 47 % control matrix so the callbacks have access without using globals 48 for c = 1:2 49 for i = 1:length(positions) 50 update_control_elements(EQ(i, c), EQ); 51 endfor 52 endfor 53 fn = figure(fn, "toolbar", "none", "menubar", "none", 'Position', [200 200 300 220]); fn = fn + 1; 54 menu_window = struct(); 55 menu_window.ip_box = uicontrol( ... 56 "style", "edit", ... 57 "position", [120 190 180 30]); 58 menu_window.ip_label = uicontrol( ... 59 "style", "text", 60 "string", "DUT IP:", ... 61 "position", [0 190 120 30]); 62 menu_window.user_box = uicontrol( ... 63 "style", "edit", ... 64 "position", [120 160 180 30]); 65 menu_window.user_label = uicontrol( ... 66 "style", "text", 67 "string", "DUT User:", ... 68 "position", [0 160 120 30]); 69 menu_window.device_box = uicontrol( ... 70 "style", "edit", ... 71 "position", [120 130 180 30]); 72 menu_window.device_label = uicontrol( ... 73 "style", "text", 74 "string", "Device Num:", ... 75 "position", [0 130 120 30]); 76 menu_window.alsa_box = uicontrol( ... 77 "style", "edit", ... 78 "position", [120 100 180 30]); 79 menu_window.alsa_label = uicontrol( ... 80 "style", "text", 81 "string", "ALSA Ctl Num:", ... 82 "position", [0 100 120 30]); 83 menu_window.upload = uicontrol( ... 84 "style", "pushbutton", ... 85 'string', 'Push config to DUT', ... 86 "callback", @push_config, ... 87 "position", [0 50 300 50], ... 88 "userdata", struct("menu_window", menu_window, "EQ", EQ)); 89 menu_window.load_dsp = uicontrol( ... 90 "style", "pushbutton", ... 91 'string', 'Load dsp.ini', ... 92 "callback", @eq_load_dsp_ini, ... 93 "position", [0 0 300 50], ... 94 "userdata", struct("EQ", EQ)); 95end 96 97function conf = get_config() 98 % since octave has no definitive way to have constants we are going to hack a 99 % struct into behaving like one 100 conf = struct( ... 101 "Fs", 48000 ... 102 ); 103end 104 105function set_udata_field(h, field, value) 106 udata = get(h, "userdata"); 107 udata = setfield(udata, field, value); 108 set(h, "userdata", udata); 109end 110 111function peq = build_peq(channel, EQ) 112 peq = []; 113 for i = 1:8 114 biquad = EQ(i, channel); 115 if get(biquad.enable_switch, "value") == 1 116 type = get(biquad.type.dropdown, "value"); 117 freq = str2num(get(biquad.freq.edit, "string")); 118 Q = str2double(get(biquad.Q.edit, "string")); 119 gain = str2double(get(biquad.gain.edit, "string")); 120 % dropdown maps straight to filter index 121 % filter only uses needed arguments, all others will be ignored 122 peq = [peq; type, freq, gain, Q]; 123 endif 124 endfor 125end 126 127function update_plot(channel, main_panes, EQ) 128 Fs = get_config().Fs; 129 nyquist_f = Fs / 2; 130 pl = main_panes(channel).plot; 131 spectrum_size = 1000; 132 n = logspace(log10(1), log10(nyquist_f), spectrum_size); 133 peq = build_peq(channel, EQ); 134 [z p k] = eq_define_parametric_eq(peq, Fs); 135 % octave gets angry when you pile up poles and zeros 136 [num den] = zp2tf(z, p, k); 137 [h, w] = freqz(num,den, n, Fs); 138 h = 20*log10(h); 139 set(pl, "YData", h, "XData", log2(w)); 140end 141 142function set_section_visibility(section, visible) 143 for [control, control_name] = section 144 set(control, "visible", visible); 145 endfor 146end 147 148function dropdown_callback(h, event) 149 udata = get(h, "userdata"); 150 EQ = udata.EQ; 151 biquad = EQ(udata.index, udata.channel); 152 eq = eq_defaults(); 153 switch (get(h, "value")) 154 case eq.PEQ_HP1 155 % highpass 1st order 156 set_section_visibility(biquad.Q, "off"); 157 set_section_visibility(biquad.gain, "off"); 158 case eq.PEQ_HP2 159 % highpass 2nd order 160 set_section_visibility(biquad.Q, "off"); 161 set_section_visibility(biquad.gain, "off"); 162 case eq.PEQ_LP1 163 % lowpass 1st order 164 set_section_visibility(biquad.Q, "off"); 165 set_section_visibility(biquad.gain, "off"); 166 case eq.PEQ_LP2 167 % lowpass 2nd order 168 set_section_visibility(biquad.Q, "off"); 169 set_section_visibility(biquad.gain, "off"); 170 case eq.PEQ_LS1 171 % lowshelf 1st order 172 set_section_visibility(biquad.Q, "off"); 173 set_section_visibility(biquad.gain, "on"); 174 case eq.PEQ_LS2 175 % lowhelf 2nd order 176 set_section_visibility(biquad.Q, "off"); 177 set_section_visibility(biquad.gain, "on"); 178 case eq.PEQ_HS1 179 % highshelf 1st order 180 set_section_visibility(biquad.Q, "off"); 181 set_section_visibility(biquad.gain, "on"); 182 case eq.PEQ_HS2 183 % highshelf 2nd order 184 set_section_visibility(biquad.Q, "off"); 185 set_section_visibility(biquad.gain, "on"); 186 case eq.PEQ_PN2 187 % peaking 188 set_section_visibility(biquad.Q, "on"); 189 set_section_visibility(biquad.gain, "on"); 190 case eq.PEQ_LP4 191 % lowpass 4th order 192 set_section_visibility(biquad.Q, "off"); 193 set_section_visibility(biquad.gain, "off"); 194 case eq.PEQ_HP4 195 % highpass 4th order 196 set_section_visibility(biquad.Q, "off"); 197 set_section_visibility(biquad.gain, "off"); 198 case eq.PEQ_LP2G 199 % lowpass 2nd order with resonnance 200 set_section_visibility(biquad.Q, "on"); 201 set_section_visibility(biquad.gain, "off"); 202 case eq.PEQ_HP2G 203 % highpass 2nd order with resonnance 204 set_section_visibility(biquad.Q, "on"); 205 set_section_visibility(biquad.gain, "off"); 206 case eq.PEQ_BP2 207 % bandpass 208 set_section_visibility(biquad.Q, "on"); 209 set_section_visibility(biquad.gain, "off"); 210 case eq.PEQ_NC2 211 % notch 212 set_section_visibility(biquad.Q, "on"); 213 set_section_visibility(biquad.gain, "off"); 214 case eq.PEQ_LS2G 215 % lowshelf, CRAS implementation 216 set_section_visibility(biquad.Q, "off"); 217 set_section_visibility(biquad.gain, "on"); 218 case eq.PEQ_HS2G 219 % highshelf, CRAS implementation 220 set_section_visibility(biquad.Q, "off"); 221 set_section_visibility(biquad.gain, "on"); 222 endswitch 223 update_plot(udata.channel, udata.main_panes, EQ); 224end 225 226function enable_callback(h, event) 227 off = get(h, "value") == 0; 228 if off 229 visible = "off"; 230 else 231 visible = "on"; 232 endif 233 udata = get(h, "userdata"); 234 EQ = udata.EQ; 235 biquad = EQ(udata.index, udata.channel); 236 for [section, section_name] = biquad 237 if strcmp(section_name, "enable_switch") || strcmp(section_name, "pane") 238 continue 239 endif 240 set_section_visibility(section, visible); 241 endfor 242 if !off 243 dropdown_callback(biquad.type.dropdown,0); 244 endif 245 update_plot(udata.channel, udata.main_panes, EQ); 246end 247 248function slider_callback(h, event) 249 udata = get(h, "userdata"); 250 EQ = udata.EQ; 251 editbox = getfield(EQ(udata.index, udata.channel), udata.prefix).edit; 252 val = get(h, "value"); 253 if udata.is_log 254 val = 0.1*2^val; 255 endif 256 set(editbox, "string", num2str(val)); 257 update_plot(udata.channel, udata.main_panes, EQ); 258end 259 260function edit_callback(h, event) 261 udata = get(h, "userdata"); 262 EQ = udata.EQ; 263 slider = getfield(EQ(udata.index, udata.channel), udata.prefix).slider; 264 val = str2num(get(h, "string")); 265 if udata.is_log && val != 0 266 val = log2(val/0.1); 267 end 268 set(slider, "value", val); 269 update_plot(udata.channel, udata.main_panes, EQ); 270end 271 272function EQ = initialize_biquad(index, channel, position, main_panes, Fs, EQ) 273 nyquist_f = Fs / 2; 274 % this string matches the order defined in eq.PEQ_*, do not change the order 275 eq_strings = {"highpass 1st order", "highpass 2nd order", ... 276 "lowpass 1st order", "lowpass 2nd order", "lowshelf 1st order", ... 277 "lowshelf 2nd order", "highshelf 1st order", "highshelf 2nd order", ... 278 "peaking 2nd order", "lowpass 4th order", "highpass 4th order", ... 279 "lowpass 2nd order (Google)", "highpass 2nd order (Google)", ... 280 "bandpass 2nd order", "notch 2nd order", "lowshelf 2nd order (Google)", ... 281 "highshelf 2nd order (Google)"}; 282 eq = eq_defaults(); 283 % UI panels, we have to use absolute positioning 284 % 285 % +-------+-----------+-------+--+--+--+--+--+----------+ 286 % | Label | Dropdown | | | | | | | checkbox | 287 % +-------+-----------+-------+--+--+--+--+--+----------+ 288 % | Label | input box | label | slider | 289 % +-------+-----------+-------+-------------------------+ 290 % | Label | input box | label | slider | 291 % +-------+-----------+-------+-------------------------+ 292 % | Label | input box | label | slider | 293 % +-------+-----------+-------+-------------------------+ 294 295 EQ(index, channel).pane = uipanel ("parent", main_panes(channel).pane, "position", position); 296 p = EQ(index, channel).pane; 297 % octave is silly and doesn't let one position controls relatively inside a uipanel 298 EQ(index, channel).gain.label = uicontrol( ... 299 "style", "text", ... 300 "parent", p, ... 301 "position", [0 10 40 30], ... 302 "visible", "off", ... 303 "string", "Gain"); 304 EQ(index, channel).gain.edit = uicontrol( ... 305 "style", "edit", ... 306 "parent", p, ... 307 "position", [40 10 90 30], ... 308 "string", "0", ... 309 "userdata", struct( ... 310 "index", index, ... 311 "channel", channel, ... 312 "prefix", "gain", ... 313 "is_log", false, ... 314 "EQ", EQ, ... 315 "main_panes", main_panes), ... 316 "visible", "off", ... 317 "callback", @edit_callback); 318 EQ(index, channel).gain.units = uicontrol( ... 319 "style", "text", ... 320 "parent", p, ... 321 "string", "dB", ... 322 "visible", "off", ... 323 "position", [140 10 17 30]); 324 EQ(index, channel).gain.slider = uicontrol( ... 325 "style", "slider", ... 326 "parent", p, ... 327 "position", [160 10 200 30], ... 328 "min", -40, ... 329 "max", 40, ... 330 "userdata", struct( 331 "index", index, ... 332 "channel", channel, ... 333 "prefix", "gain", ... 334 "is_log", false, ... 335 "EQ", EQ, ... 336 "main_panes", main_panes), ... 337 "visible", "off", ... 338 "callback", @slider_callback); 339 340 EQ(index, channel).Q.label = uicontrol( ... 341 "style", "text", ... 342 "parent", p, ... 343 "position", [0 50 50 30], ... 344 "visible", "off", ... 345 "string", "Q"); 346 EQ(index, channel).Q.edit = uicontrol( ... 347 "style", "edit", ... 348 "parent", p, ... 349 "position", [40 50 90 30], ... 350 "string", "1", ... 351 "userdata", struct( ... 352 "index", index, ... 353 "channel", channel, ... 354 "prefix", "Q", ... 355 "is_log", true, ... 356 "EQ", EQ, ... 357 "main_panes", main_panes), ... 358 "visible", "off", ... 359 "callback", @edit_callback); 360 EQ(index, channel).Q.slider = uicontrol( ... 361 "style", "slider", ... 362 "parent", p, ... 363 "position", [160 50 200 30], ... 364 "min", 0, ... 365 "max", log2(1000/0.1), ... 366 "value", log2(1/0.1), ... 367 "userdata", struct( 368 "index", index, ... 369 "channel", channel, ... 370 "prefix", "Q", ... 371 "is_log", true, ... 372 "EQ", EQ, ... 373 "main_panes", main_panes), ... 374 "visible", "off", ... 375 "callback", @slider_callback); 376 377 EQ(index, channel).freq.label = uicontrol( ... 378 "style", "text", ... 379 "parent", p, ... 380 "position", [0 90 40 30], ... 381 "visible", "off", ... 382 "string", "Freq"); 383 EQ(index, channel).freq.edit = uicontrol( ... 384 "style", "edit", ... 385 "parent", p, ... 386 "position", [40 90 90 30], ... 387 "string", "350", ... 388 "userdata", struct( ... 389 "index", index, ... 390 "channel", channel, ... 391 "prefix", "freq", ... 392 "is_log", true, ... 393 "EQ", EQ, ... 394 "main_panes", main_panes), ... 395 "visible", "off", ... 396 "callback", @edit_callback); 397 EQ(index, channel).freq.units = uicontrol( ... 398 "style", "text", ... 399 "parent", p, ... 400 "position", [140 90 17 30], ... 401 "visible", "off", ... 402 "string", "Hz"); 403 EQ(index, channel).freq.slider = uicontrol( ... 404 "style", "slider", ... 405 "parent", p, ... 406 "position", [160 90 200 30], ... 407 "min", 1, ... 408 "max", log2(nyquist_f/0.1), ... 409 "value", log2(350/0.1), ... 410 "userdata", struct( 411 "index", index, ... 412 "channel", channel, ... 413 "prefix", "freq", ... 414 "is_log", true, ... 415 "EQ", EQ, ..., 416 "main_panes", main_panes), ... 417 "visible", "off", ... 418 "callback", @slider_callback); 419 420 EQ(index, channel).type.label = uicontrol( ... 421 "style", "text", ... 422 "parent", p, ... 423 "position", [0 130 40 30], ... 424 "visible", "off", ... 425 "string", "Type"); 426 EQ(index, channel).type.dropdown = uicontrol( ... 427 "style", "popupmenu", ... 428 "parent", p, ... 429 "position", [40 130 240 30], ... 430 "string", eq_strings, ... 431 "value", eq.PEQ_PN2, ... 432 "userdata", struct( ... 433 "index", index, ... 434 "channel", channel, ... 435 "EQ", EQ, ... 436 "main_panes", main_panes), ... 437 "visible", "off", ... 438 "callback", @dropdown_callback); 439 EQ(index, channel).enable_switch = uicontrol( ... 440 "style", "checkbox", ... 441 "parent", p, ... 442 "position", [350 130 30 30], ... 443 "value", 0, ... 444 "userdata", struct( 445 "index", index, ... 446 "channel", channel, ... 447 "EQ", EQ, ... 448 "main_panes", main_panes), ... 449 "callback", @enable_callback); 450end 451 452function push_config(h, evnt) 453 udata = get(h, "userdata"); 454 window = udata.menu_window; 455 EQ = udata.EQ; 456 ip = get(window.ip_box, "string"); 457 user = get(window.user_box, "string"); 458 device = str2num(get(window.device_box, "string")); 459 alsa_num = str2num(get(window.alsa_box, "string")); 460 target = struct("ip", ip, "user", user, "device", device, "control", alsa_num); 461 462 temp_file = tempname(); 463 464 eq_left = eq_defaults(); 465 eq_left.enable_iir = 1; 466 eq_left.peq = build_peq(1, EQ); 467 eq_left.norm_type = 'peak'; 468 eq_left.norm_offs_db = 0; 469 eq_left.fs = get_config().Fs; 470 471 eq_right = eq_defaults(); 472 eq_right.enable_iir = 1; 473 eq_right.peq = build_peq(2, EQ); 474 eq_right.norm_type = 'peak'; 475 eq_right.norm_offs_db = 0; 476 eq_right.fs = get_config().Fs; 477 478 eq_left = eq_compute(eq_left); 479 eq_right = eq_compute(eq_right); 480 481 bq_left = eq_iir_blob_quant(eq_left.p_z, eq_left.p_p, eq_left.p_k); 482 bq_right = eq_iir_blob_quant(eq_right.p_z, eq_right.p_p, eq_right.p_k); 483 484 channels_in_config = 2; 485 assign_response = [0 1]; 486 num_responses = 2; 487 bm = eq_iir_blob_merge(channels_in_config, ... 488 num_responses, ... 489 assign_response, ... 490 [bq_left bq_right]); 491 492 bp = eq_iir_blob_pack(bm); 493 eq_alsactl_write(temp_file, bp); 494 eq_deploy_to_dut(target, temp_file); 495end 496 497function update_control_elements(section, EQ) 498 set_udata_field(section.gain.edit, "EQ", EQ); 499 set_udata_field(section.gain.slider, "EQ", EQ); 500 set_udata_field(section.Q.edit, "EQ", EQ); 501 set_udata_field(section.Q.slider, "EQ", EQ); 502 set_udata_field(section.freq.edit, "EQ", EQ); 503 set_udata_field(section.freq.slider, "EQ", EQ); 504 set_udata_field(section.type.dropdown, "EQ", EQ); 505 set_udata_field(section.enable_switch, "EQ", EQ); 506end 507 508