Merge pull request #61 from singalsu/fix_matlab_iir_eq_proposal

EQ Tool: Fix compatibility with Matlab and improve accuracy of IIR filters
This commit is contained in:
Liam Girdwood 2018-08-30 12:47:55 +01:00 committed by GitHub
commit eddca597ae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 148 additions and 85 deletions

View File

@ -42,9 +42,9 @@ eq = preprocess_responses(eq);
%% Define target (e.g. speaker) response as parametric filter. This could also %% Define target (e.g. speaker) response as parametric filter. This could also
% be numerical data interpolated to the grid. % be numerical data interpolated to the grid.
if length(eq.parametric_target_response) > 0 if length(eq.parametric_target_response) > 0
[eq.b_t, eq.a_t] = eq_define_parametric_eq( ... [eq.t_z, eq.t_p, eq.t_k] = eq_define_parametric_eq( ...
eq.parametric_target_response, eq.fs); eq.parametric_target_response, eq.fs);
eq.t_db = eq_compute_response(eq.b_t, eq.a_t, eq.f, eq.fs); eq.t_db = eq_compute_response(eq.t_z, eq.t_p, eq.t_k, eq.f, eq.fs);
end end
if isempty(eq.t_db) if isempty(eq.t_db)
@ -64,16 +64,15 @@ eq.err_db = eq.t_db - eq.m_db;
%% Parametric IIR EQ definition %% Parametric IIR EQ definition
if eq.enable_iir if eq.enable_iir
[eq.b_p, eq.a_p] = eq_define_parametric_eq(eq.peq, eq.fs); [eq.p_z, eq.p_p, eq.p_k] = eq_define_parametric_eq(eq.peq, eq.fs);
if length(eq.b_p) > 2*eq.iir_biquads_max+1 if max(length(eq.p_z), length(eq.p_p)) > 2*eq.iir_biquads_max
error('Maximum number of IIR biquads is exceeded'); error('Maximum number of IIR biquads is exceeded');
end end
else else
eq.b_p = 1; [eq.p_z, eq.p_p, eq.p_k] = tf2zp(1, 1);
eq.a_p = 1;
end end
[eq.iir_eq_db, eq.iir_eq_ph, eq.iir_eq_gd] = eq_compute_response(eq.b_p, ... [eq.iir_eq_db, eq.iir_eq_ph, eq.iir_eq_gd] = eq_compute_response(eq.p_z, ...
eq.a_p, eq.f, eq.fs); eq.p_p, eq.p_k, eq.f, eq.fs);
%% FIR EQ computation %% FIR EQ computation

View File

@ -1,4 +1,4 @@
function [m, ph, gd] = eq_compute_response(b, a, f, fs) function [m, ph, gd] = eq_compute_response(z, p, k, f, fs)
%% %%
% Copyright (c) 2016, Intel Corporation % Copyright (c) 2016, Intel Corporation
@ -30,16 +30,43 @@ function [m, ph, gd] = eq_compute_response(b, a, f, fs)
% Author: Seppo Ingalsuo <seppo.ingalsuo@linux.intel.com> % Author: Seppo Ingalsuo <seppo.ingalsuo@linux.intel.com>
% %
h = freqz(b, a, f, fs); switch nargin
m = 20*log10(abs(h)); case 3
ph = 180/pi*angle(h); b = z; % 1st arg
f = p; % 2nd arg
fs = k; % 3rd arg
h = freqz(b, 1, f, fs);
m = 20*log10(abs(h));
ph = 180/pi*angle(h);
lb = length(b); if length(b) == 1
la = length(a); gd = zeros(1, length(f));
if lb == 1 && la == 1 else
gd = zeros(1, length(f)); if exist('OCTAVE_VERSION', 'builtin')
else % grpdelay() has some issue so better to not show a plot
gd = 1/fs*grpdelay(b, a, f, fs); gd = NaN * zeros(1, length(f));
else
gd = 1/fs*grpdelay(b, 1, f, fs);
end
end
case 5
[b, a] = zp2tf(z, p, k);
h = freqz(b, a, f, fs);
m = 20*log10(abs(h));
ph = 180/pi*angle(h);
if length(z) == 0 && length(p) == 0
gd = zeros(1, length(f));
else
if exist('OCTAVE_VERSION', 'builtin')
% grpdelay() has some issue so better to not show a plot
gd = NaN * zeros(1, length(f));
else
gd = 1/fs*grpdelay(b, a, f, fs);
end
end
otherwise
error('Incorrect input parameters');
end end
end end

View File

@ -31,8 +31,8 @@ function eq = eq_compute_tot_response(eq)
% %
% FIR response and combined IIR+FIR EQ % FIR response and combined IIR+FIR EQ
[eq.iir_eq_db, eq.iir_eq_ph, eq.iir_eq_gd] = eq_compute_response(eq.b_p, eq.a_p, eq.f, eq.fs); [eq.iir_eq_db, eq.iir_eq_ph, eq.iir_eq_gd] = eq_compute_response(eq.p_z, eq.p_p, eq.p_k, eq.f, eq.fs);
[eq.fir_eq_db, eq.fir_eq_ph, eq.fir_eq_gd] = eq_compute_response(eq.b_fir, 1, eq.f, eq.fs); [eq.fir_eq_db, eq.fir_eq_ph, eq.fir_eq_gd] = eq_compute_response(eq.b_fir, eq.f, eq.fs);
eq.tot_eq_db = eq.iir_eq_db + eq.fir_eq_db; eq.tot_eq_db = eq.iir_eq_db + eq.fir_eq_db;
eq.tot_eq_gd = eq.iir_eq_gd + eq.fir_eq_gd; eq.tot_eq_gd = eq.iir_eq_gd + eq.fir_eq_gd;

View File

@ -58,7 +58,7 @@ p.fir_autoband = 1;
p.enable_fir = 0; p.enable_fir = 0;
% IIR conf % IIR conf
p.iir_biquads_max = 6; p.iir_biquads_max = 8;
p.enable_iir = 0; p.enable_iir = 0;
% Initialize other fields those are computed later to allow use of struct % Initialize other fields those are computed later to allow use of struct
@ -73,15 +73,17 @@ p.num_responses = 0;
p.m_db_s = []; p.m_db_s = [];
p.gd_s_s = []; p.gd_s_s = [];
p.logsmooth_noct = 0; p.logsmooth_noct = 0;
p.b_t = []; p.t_z = [];
p.a_t = []; p.t_p = [];
p.t_k = [];
p.t_db = []; p.t_db = [];
p.m_db_abs = 0; p.m_db_abs = 0;
p.m_db_offs = 0; p.m_db_offs = 0;
p.raw_m_noalign_db = []; p.raw_m_noalign_db = [];
p.err_db = []; p.err_db = [];
p.b_p = 0; p.p_z = [];
p.a_p = 0; p.p_p = [];
p.p_k = [];
p.iir_eq_db = []; p.iir_eq_db = [];
p.iir_eq_ph = []; p.iir_eq_ph = [];
p.iir_eq_gd = []; p.iir_eq_gd = [];

View File

@ -1,4 +1,4 @@
function [b_t, a_t] = eq_define_parametric_eq(peq, fs) function [z, p, k] = eq_define_parametric_eq(peq, fs)
%% %%
% Copyright (c) 2016, Intel Corporation % Copyright (c) 2016, Intel Corporation
@ -36,20 +36,27 @@ PEQ_LS1 = 5; PEQ_LS2 = 6; PEQ_HS1 = 7; PEQ_HS2 = 8;
PEQ_PN2 = 9; PEQ_LP4 = 10; PEQ_HP4 = 11; PEQ_PN2 = 9; PEQ_LP4 = 10; PEQ_HP4 = 11;
sp = size(peq); sp = size(peq);
b_t = 1; a_t = 1; z = [];
p = [];
k = 1;
for i=1:sp(1) for i=1:sp(1)
type = peq(i,1); type = peq(i,1);
f = peq(i,2); f = peq(i,2);
g = peq(i,3); g = peq(i,3);
Q = peq(i,4); Q = peq(i,4);
if f < fs/2 if f < fs/2
a0 = [];
b0 = [];
z0 = [];
p0 = [];
k0 = [];
switch peq(i,1) switch peq(i,1)
case PEQ_HP1, [b0, a0] = butter(1, 2*f/fs, 'high'); case PEQ_HP1, [z0, p0, k0] = butter(1, 2*f/fs, 'high');
case PEQ_HP2, [b0, a0] = butter(2, 2*f/fs, 'high'); case PEQ_HP2, [z0, p0, k0] = butter(2, 2*f/fs, 'high');
case PEQ_HP4, [b0, a0] = butter(4, 2*f/fs, 'high'); case PEQ_HP4, [z0, p0, k0] = butter(4, 2*f/fs, 'high');
case PEQ_LP1, [b0, a0] = butter(1, 2*f/fs); case PEQ_LP1, [z0, p0, k0] = butter(1, 2*f/fs);
case PEQ_LP2, [b0, a0] = butter(2, 2*f/fs); case PEQ_LP2, [z0, p0, k0] = butter(2, 2*f/fs);
case PEQ_LP4, [b0, a0] = butter(4, 2*f/fs); case PEQ_LP4, [z0, p0, k0] = butter(4, 2*f/fs);
case PEQ_LS1, [b0, a0] = low_shelf_1st(f, g, fs); case PEQ_LS1, [b0, a0] = low_shelf_1st(f, g, fs);
case PEQ_LS2, [b0, a0] = low_shelf_2nd(f, g, fs); case PEQ_LS2, [b0, a0] = low_shelf_2nd(f, g, fs);
case PEQ_HS1, [b0, a0] = high_shelf_1st(f, g, fs); case PEQ_HS1, [b0, a0] = high_shelf_1st(f, g, fs);
@ -58,7 +65,14 @@ for i=1:sp(1)
otherwise otherwise
error('Unknown parametric EQ type'); error('Unknown parametric EQ type');
end end
b_t=conv(b_t, b0); a_t = conv(a_t, a0); if length(a0) > 0
[z0, p0, k0] = tf2zp(b0, a0);
end
if length(k0) > 0
z = [z ; z0(:)];
p = [p ; p0(:)];
k = k * k0;
end
end end
end end
end end

View File

@ -1,12 +1,12 @@
function iir_resp = eq_iir_blob_quant(eq_b, eq_a) function iir_resp = eq_iir_blob_quant(eq_z, eq_p, eq_k)
%% Convert IIR coefficients to 2nd order sections and quantize %% Convert IIR coefficients to 2nd order sections and quantize
% %
% iir_resp = eq_iir_blob_quant(b, a, bits) % iir_resp = eq_iir_blob_quant(z, p, k)
% %
% b - numerator coefficients % z - zeros
% a - denominator coefficients % p - poles
% bits - number of bits to quantize % k - gain
% %
% iir_resp - vector to setup an IIR equalizer with number of sections, shifts, % iir_resp - vector to setup an IIR equalizer with number of sections, shifts,
% and quantized coefficients % and quantized coefficients
@ -47,21 +47,41 @@ bits_iir = 32; % Q2.30
qf_iir = 30; qf_iir = 30;
bits_gain = 16; % Q2.14 bits_gain = 16; % Q2.14
qf_gain = 14; qf_gain = 14;
scale_max = -3; % dB, scale biquad L-inf norm scale_max = -6; % dB, scale biquads peak gain to this
plot_pz = 0; plot_pz = 0;
plot_fr = 0; plot_fr = 0;
%% Convert IIR to 2nd order sections %% Convert IIR to 2nd order sections
if exist('OCTAVE_VERSION', 'builtin') % This a simple implementation of zp2sos() function. It is not used here due
[sos, gain] = tf2sos(eq_b, eq_a); % to utilization of rather strong scaling and resulting low SNR with the
else % available word length in EQ in SOF. This poles and zeros allocation to
% TODO: tf2sos produces in Matlab EQs with zero output due to % biquads is base only in ascending sort of angular frequency.
% very high gain variations within biquad. Need to investigate if sz = length(eq_z);
% this is incorrect usage and a bug here. sp = length(eq_p);
[sos, gain] = tf2sos(eq_b, eq_a, 'UP', Inf); sk = length(eq_k);
fprintf('Warning: Problems have been seen with some IIR designs.\n'); nb = max(sz, sp)/2;
fprintf('Warning: Please check the result.\n'); az = angle(eq_z);
ap = angle(eq_p);
[~, iz] = sort(abs(az));
[~, ip] = sort(abs(ap));
eq_z = eq_z(iz);
eq_p = eq_p(ip);
sos = zeros(nb, 6);
for i = 1:nb
j = 2*(i-1)+1;
if i == 1
[b, a] = zp2tf(eq_z(j:j+1), eq_p(j:j+1), eq_k);
else
[b, a] = zp2tf(eq_z(j:j+1), eq_p(j:j+1), 1);
end
sos(i,1:3) = b;
sos(i,4:6) = a;
end end
gain = 1;
%% Convert 2nd order sections to SOF parameters format and scale the biquads
% with criteria below (Gain max -6 dB at any frequency). Then calculate
% scaling shifts and finally gain multiplier for output.
sz = size(sos); sz = size(sos);
nbr_sections = sz(1); nbr_sections = sz(1);
n_section_header = 2; n_section_header = 2;
@ -70,10 +90,10 @@ iir_resp = int32(zeros(1,n_section_header+nbr_sections*n_section));
iir_resp(1) = nbr_sections; iir_resp(1) = nbr_sections;
iir_resp(2) = nbr_sections; % Note: All sections in series iir_resp(2) = nbr_sections; % Note: All sections in series
scale_max_lin = 10^(scale_max/20);
for n=1:nbr_sections for n=1:nbr_sections
b = sos(n,1:3); b = sos(n,1:3);
a = sos(n,4:6); a = sos(n,4:6);
if plot_pz if plot_pz
figure figure
zplane(b,a); zplane(b,a);
@ -84,14 +104,12 @@ for n=1:nbr_sections
np = 1024; np = 1024;
[h, w] = freqz(b, a, np); [h, w] = freqz(b, a, np);
hm = max(abs(h)); hm = max(abs(h));
scale = 10^(scale_max/20)/hm; scale = scale_max_lin/hm;
gain_remain = 1/scale; gain_remain = 1/scale;
gain = gain*gain_remain; gain = gain*gain_remain;
b = b * scale; b = b * scale;
ma = max(abs(a)); ma = max(abs(a));
mb = max(abs(b)); mb = max(abs(b));
if plot_fr if plot_fr
figure figure
[h, w] = freqz(b, a, np); [h, w] = freqz(b, a, np);
@ -119,6 +137,8 @@ for n=1:nbr_sections
iir_resp(m+5) = section_shift; iir_resp(m+5) = section_shift;
iir_resp(m+6) = eq_coef_quant( section_gain, bits_gain, qf_gain); iir_resp(m+6) = eq_coef_quant( section_gain, bits_gain, qf_gain);
%fprintf('sec=%d, shift=%d, gain=%f\n', n, section_shift, section_gain);
end end
end end

View File

@ -61,13 +61,13 @@ end
%% Adjust FIR and IIR gains if enabled %% Adjust FIR and IIR gains if enabled
if eq.enable_fir && eq.enable_iir if eq.enable_fir && eq.enable_iir
eq.b_fir = eq.b_fir * g_fir * g_offs; eq.b_fir = eq.b_fir * g_fir * g_offs;
eq.b_p = eq.b_p * g_iir * g_offs; eq.p_k = eq.p_k * g_iir * g_offs;
end end
if eq.enable_fir && eq.enable_iir == 0 if eq.enable_fir && eq.enable_iir == 0
eq.b_fir = eq.b_fir * g_fir * g_offs; eq.b_fir = eq.b_fir * g_fir * g_offs;
end end
if eq.enable_fir == 0 && eq.enable_iir if eq.enable_fir == 0 && eq.enable_iir
eq.b_p = eq.b_p * g_iir * g_offs; eq.p_k = eq.p_k * g_iir * g_offs;
end end
%% Re-compute response after adjusting gain %% Re-compute response after adjusting gain

View File

@ -115,7 +115,7 @@ if length(eq.b_fir) > 1
end end
%% IIR filter %% IIR filter
if length(eq.b_p) > 1 if length(eq.p_z) > 1 || length(eq.p_p) > 1
% Response % Response
fh=figure(fn); fn = fn+1; fh=figure(fn); fn = fn+1;
semilogx(eq.f, eq.iir_eq_db); semilogx(eq.f, eq.iir_eq_db);
@ -128,7 +128,7 @@ if length(eq.b_p) > 1
% Polar % Polar
fh=figure(fn); fn = fn+1; fh=figure(fn); fn = fn+1;
zplane(eq.b_p, eq.a_p); zplane(eq.p_z, eq.p_p);
grid on; grid on;
tstr = sprintf('IIR zeros and poles: %s', eq.name); tstr = sprintf('IIR zeros and poles: %s', eq.name);
title(tstr); title(tstr);
@ -137,7 +137,8 @@ if length(eq.b_p) > 1
ti = 50e-3; ti = 50e-3;
x = zeros(1, ti * round(eq.fs)); x = zeros(1, ti * round(eq.fs));
x(1) = 1; x(1) = 1;
y = filter(eq.b_p, eq.a_p, x); sos = zp2sos(eq.p_z, eq.p_p, eq.p_k);
y = sosfilt(sos, x);
fh=figure(fn); fn = fn+1; fh=figure(fn); fn = fn+1;
t = (0:(length(x)-1)) / eq.fs; t = (0:(length(x)-1)) / eq.fs;
plot(t, y); plot(t, y);
@ -149,28 +150,29 @@ if length(eq.b_p) > 1
end end
%% Group delay %% Group delay
fh=figure(fn); fn = fn+1; if ~exist('OCTAVE_VERSION', 'builtin')
if eq.enable_fir && eq.enable_iir % Skip plot if running in Octave due to incorrect result
semilogx(eq.f, eq.tot_eq_gd * 1e3, ... fh=figure(fn); fn = fn+1;
eq.f, eq.fir_eq_gd * 1e3, '--', ... if eq.enable_fir && eq.enable_iir
eq.f, eq.iir_eq_gd * 1e3, '--'); semilogx(eq.f, eq.tot_eq_gd * 1e3, ...
legend('Combined','FIR','IIR'); eq.f, eq.fir_eq_gd * 1e3, '--', ...
eq.f, eq.iir_eq_gd * 1e3, '--');
legend('Combined','FIR','IIR');
end
if eq.enable_fir && eq.enable_iir == 0
semilogx(eq.f, eq.fir_eq_gd * 1e3);
legend('FIR');
end
if eq.enable_fir == 0 && eq.enable_iir
semilogx(eq.f, eq.iir_eq_gd * 1e3);
legend('IIR');
end
grid on;
xlabel('Frequency (Hz)');
ylabel('Group delay (ms)');
ax = axis; axis([eq.p_fmin eq.p_fmax ax(3:4)]);
tstr = sprintf('Filter group delay: %s', eq.name);
title(tstr);
end end
if eq.enable_fir && eq.enable_iir == 0
semilogx(eq.f, eq.fir_eq_gd * 1e3);
legend('FIR');
end
if eq.enable_fir == 0 && eq.enable_iir
semilogx(eq.f, eq.iir_eq_gd * 1e3);
legend('IIR');
end
grid on;
xlabel('Frequency (Hz)');
ylabel('Group delay (ms)');
ax = axis; axis([eq.p_fmin eq.p_fmax ax(3:4)]);
tstr = sprintf('Filter group delay: %s', eq.name);
title(tstr);
end end

View File

@ -41,12 +41,11 @@ fs = 48e3;
eq_loud = loudness_iir_eq(fs); eq_loud = loudness_iir_eq(fs);
%% Define a passthru IIR EQ equalizer %% Define a passthru IIR EQ equalizer
b_pass = [1 0 0]; [z_pass, p_pass, k_pass] = tf2zp([1 0 0],[1 0 0]);
a_pass = [1 0 0];
%% Quantize and pack filter coefficients plus shifts etc. %% Quantize and pack filter coefficients plus shifts etc.
bq_pass = eq_iir_blob_quant(b_pass, a_pass); bq_pass = eq_iir_blob_quant(z_pass, p_pass, k_pass);
bq_loud = eq_iir_blob_quant(eq_loud.b_p, eq_loud.a_p); bq_loud = eq_iir_blob_quant(eq_loud.p_z, eq_loud.p_p, eq_loud.p_k);
%% Build blob %% Build blob
platform_max_channels = 4; % Setup max 4 channels EQ platform_max_channels = 4; % Setup max 4 channels EQ

View File

@ -140,7 +140,7 @@ end
%% Export IIR part %% Export IIR part
if eq.enable_iir if eq.enable_iir
bq_iir = eq_iir_blob_quant(eq.b_p, eq.a_p); bq_iir = eq_iir_blob_quant(eq.p_z, eq.p_p, eq.p_k);
bm_iir = eq_iir_blob_merge(platform_max_channels, ... bm_iir = eq_iir_blob_merge(platform_max_channels, ...
num_responses, ... num_responses, ...
assign_response, ... assign_response, ...