|
@@ -1,10 +1,11 @@
|
|
|
|
|
+%% -*- mode: octave -*-
|
|
|
%%
|
|
%%
|
|
|
%% *SpHEAR Project
|
|
%% *SpHEAR Project
|
|
|
%%
|
|
%%
|
|
|
%% sphear_fir_design: design a matrix of FIR filters that equalizes the microphone
|
|
%% sphear_fir_design: design a matrix of FIR filters that equalizes the microphone
|
|
|
%%
|
|
%%
|
|
|
%% usage:
|
|
%% usage:
|
|
|
-%% [FIR] = sphear_fir_design(F, A2BF, n, Fs, Fl, plot_on, W, X, Y, Z)
|
|
|
|
|
|
|
+%% [FIR] = sphear_fir_design(F, A2BF, n, maxboost, Fs, Fl, plot_on, W, X, Y, Z)
|
|
|
%%
|
|
%%
|
|
|
%% F: array of filter section center frequencies
|
|
%% F: array of filter section center frequencies
|
|
|
%% A2BF: vector of A2B matrixes, one for each frequency
|
|
%% A2BF: vector of A2B matrixes, one for each frequency
|
|
@@ -63,66 +64,122 @@ function [FIR] = sphear_fir_design(F, A2BF, n, maxboost, Fs, Fl, plot_on, W, X,
|
|
|
%% create a frequency response profile in dB
|
|
%% create a frequency response profile in dB
|
|
|
fr_profile = interp1([F(1), F(fl_idx), F(end)], [0, 0, 0], F);
|
|
fr_profile = interp1([F(1), F(fl_idx), F(end)], [0, 0, 0], F);
|
|
|
|
|
|
|
|
|
|
+ %% boost high frequencies
|
|
|
|
|
+ [boost_l_B, boost_l_A] = boost(2.0, 8000, 15000, Fs);
|
|
|
|
|
+ [boost_h_B, boost_h_A] = boost(2.0, 12000, 10000, Fs);
|
|
|
|
|
+ [boost_l_h, boost_l_w] = freqz(boost_l_B, boost_l_A);
|
|
|
|
|
+ [boost_h_h, boost_h_w] = freqz(boost_h_B, boost_h_A);
|
|
|
|
|
+ %%fr_profile = interp1(boost_l_w/pi*(Fs/2), 20*log10(abs(boost_l_h)) .+ 20*log10(abs(boost_h_h)), F);
|
|
|
|
|
+
|
|
|
dmax = size(A2BF(:,1,1))(1);
|
|
dmax = size(A2BF(:,1,1))(1);
|
|
|
- %% all b format components
|
|
|
|
|
|
|
+ %% calculate running averages of WXYZ filters and A2B matrices
|
|
|
for b = 1:4
|
|
for b = 1:4
|
|
|
%% all capsules
|
|
%% all capsules
|
|
|
for c = 1:4
|
|
for c = 1:4
|
|
|
- %% all frequency bands
|
|
|
|
|
|
|
+ %% all frequency bands going down
|
|
|
|
|
+ for d = dmax:-1:1
|
|
|
|
|
+ if (F(d) > Fl)
|
|
|
|
|
+ %% running average of A2B matrices
|
|
|
|
|
+ if (d == dmax)
|
|
|
|
|
+ upper_a2b(c, b, :) = A2BF(d, c, b);
|
|
|
|
|
+ else
|
|
|
|
|
+ if (d < dmax)
|
|
|
|
|
+ upper_a2b(c, b, :) = (upper_a2b(c, b, :) + A2BF(d, c, b)) / 2;
|
|
|
|
|
+ end
|
|
|
|
|
+ end
|
|
|
|
|
+ if (exist("W") == 1)
|
|
|
|
|
+ %% running average of the filter values
|
|
|
|
|
+ if (d == dmax)
|
|
|
|
|
+ upper_w(c, b) = W(d);
|
|
|
|
|
+ upper_x(c, b) = X(d);
|
|
|
|
|
+ upper_y(c, b) = Y(d);
|
|
|
|
|
+ upper_z(c, b) = Z(d);
|
|
|
|
|
+ else
|
|
|
|
|
+ if (d < dmax)
|
|
|
|
|
+ upper_w(c, b) = (upper_w(c, b) + W(d)) / 2;
|
|
|
|
|
+ upper_x(c, b) = (upper_x(c, b) + X(d)) / 2;
|
|
|
|
|
+ upper_y(c, b) = (upper_y(c, b) + Y(d)) / 2;
|
|
|
|
|
+ upper_z(c, b) = (upper_z(c, b) + Z(d)) / 2;
|
|
|
|
|
+ end
|
|
|
|
|
+ end
|
|
|
|
|
+ end
|
|
|
|
|
+ end
|
|
|
|
|
+ end
|
|
|
|
|
+ %% all frequency bands going up
|
|
|
for d = 1:dmax
|
|
for d = 1:dmax
|
|
|
- %% create an array of points to design the filter
|
|
|
|
|
if (F(d) <= Fl)
|
|
if (F(d) <= Fl)
|
|
|
- %% below the limit frequency we use the A2B coefficients
|
|
|
|
|
- vlast = A2BF(d, c, b);
|
|
|
|
|
- %% apply frequency response profile
|
|
|
|
|
- A2BF(d, c, b) = vlast * 10^(fr_profile(d)/20);
|
|
|
|
|
- dlast = d;
|
|
|
|
|
|
|
+ %% running average of A2B matrices
|
|
|
|
|
+ if (d == 1)
|
|
|
|
|
+ lower_a2b(c, b, :) = A2BF(d, c, b);
|
|
|
|
|
+ else
|
|
|
|
|
+ if (d > 1)
|
|
|
|
|
+ lower_a2b(c, b, :) = (lower_a2b(c, b, :) + A2BF(d, c, b)) / 2;
|
|
|
|
|
+ end
|
|
|
|
|
+ end
|
|
|
if (exist("W") == 1)
|
|
if (exist("W") == 1)
|
|
|
|
|
+ %% running average of the filter values
|
|
|
if (d == 1)
|
|
if (d == 1)
|
|
|
- wlast = W(d);
|
|
|
|
|
- xlast = X(d);
|
|
|
|
|
- ylast = Y(d);
|
|
|
|
|
- zlast = Z(d);
|
|
|
|
|
|
|
+ lower_w(c, b) = W(d);
|
|
|
|
|
+ lower_x(c, b) = X(d);
|
|
|
|
|
+ lower_y(c, b) = Y(d);
|
|
|
|
|
+ lower_z(c, b) = Z(d);
|
|
|
else
|
|
else
|
|
|
if (d > 1)
|
|
if (d > 1)
|
|
|
- wlast = (wlast + W(d)) / 2;
|
|
|
|
|
- xlast = (xlast + X(d)) / 2;
|
|
|
|
|
- ylast = (ylast + Y(d)) / 2;
|
|
|
|
|
- zlast = (zlast + Z(d)) / 2;
|
|
|
|
|
|
|
+ lower_w(c, b) = (lower_w(c, b) + W(d)) / 2;
|
|
|
|
|
+ lower_x(c, b) = (lower_x(c, b) + X(d)) / 2;
|
|
|
|
|
+ lower_y(c, b) = (lower_y(c, b) + Y(d)) / 2;
|
|
|
|
|
+ lower_z(c, b) = (lower_z(c, b) + Z(d)) / 2;
|
|
|
end
|
|
end
|
|
|
end
|
|
end
|
|
|
end
|
|
end
|
|
|
|
|
+ end
|
|
|
|
|
+ end
|
|
|
|
|
+ end
|
|
|
|
|
+ end
|
|
|
|
|
+
|
|
|
|
|
+ %% all b format components
|
|
|
|
|
+ for b = 1:4
|
|
|
|
|
+ %% all capsules
|
|
|
|
|
+ for c = 1:4
|
|
|
|
|
+ %% all frequency bands
|
|
|
|
|
+ for d = 1:dmax
|
|
|
|
|
+ %% create an array of points to design the filter
|
|
|
|
|
+ if (F(d) <= Fl)
|
|
|
|
|
+ dlast = d;
|
|
|
|
|
+ %% apply frequency response profile
|
|
|
|
|
+ A2BF(d, c, b) = A2BF(d, c, b) * 10^(fr_profile(d)/20);
|
|
|
else
|
|
else
|
|
|
%% above the limit frequency we insert the W, X, Y and Z
|
|
%% above the limit frequency we insert the W, X, Y and Z
|
|
|
%% data if they exist, otherwise keep the last valid value
|
|
%% data if they exist, otherwise keep the last valid value
|
|
|
%% from the A2B coefficients (ie: flat above the transition
|
|
%% from the A2B coefficients (ie: flat above the transition
|
|
|
%% frequency
|
|
%% frequency
|
|
|
|
|
+ limit_a2b = ((lower_a2b(c, b, :) + lower_a2b(c, b, :)) / 2);
|
|
|
if (b == 1)
|
|
if (b == 1)
|
|
|
if (exist("W") == 1)
|
|
if (exist("W") == 1)
|
|
|
- A2BF(d, c, b) = vlast * (W(d)/wlast) * 10^(fr_profile(d)/20);
|
|
|
|
|
|
|
+ A2BF(d, c, b) = limit_a2b * (W(d)/((lower_w(c, b) + upper_w(c, b)) / 2)) * 10^(fr_profile(d)/20);
|
|
|
else
|
|
else
|
|
|
- A2BF(d, c, b) = vlast;
|
|
|
|
|
|
|
+ A2BF(d, c, b) = limit_a2b;
|
|
|
end
|
|
end
|
|
|
end
|
|
end
|
|
|
if (b == 2)
|
|
if (b == 2)
|
|
|
if (exist("X") == 1)
|
|
if (exist("X") == 1)
|
|
|
- A2BF(d, c, b) = vlast * (X(d)/xlast) * 10^(fr_profile(d)/20);
|
|
|
|
|
|
|
+ A2BF(d, c, b) = limit_a2b * (X(d)/((lower_x(c, b) + upper_x(c, b)) / 2)) * 10^(fr_profile(d)/20);
|
|
|
else
|
|
else
|
|
|
- A2BF(d, c, b) = vlast;
|
|
|
|
|
|
|
+ A2BF(d, c, b) = limit_a2b;
|
|
|
end
|
|
end
|
|
|
end
|
|
end
|
|
|
if (b == 3)
|
|
if (b == 3)
|
|
|
if (exist("Y") == 1)
|
|
if (exist("Y") == 1)
|
|
|
- A2BF(d, c, b) = vlast * (Y(d)/ylast) * 10^(fr_profile(d)/20);
|
|
|
|
|
|
|
+ A2BF(d, c, b) = limit_a2b * (Y(d)/((lower_y(c, b) + upper_y(c, b)) / 2)) * 10^(fr_profile(d)/20);
|
|
|
else
|
|
else
|
|
|
- A2BF(d, c, b) = vlast;
|
|
|
|
|
|
|
+ A2BF(d, c, b) = limit_a2b;
|
|
|
end
|
|
end
|
|
|
end
|
|
end
|
|
|
if (b == 4)
|
|
if (b == 4)
|
|
|
if (exist("Z") == 1)
|
|
if (exist("Z") == 1)
|
|
|
- A2BF(d, c, b) = vlast * (Z(d)/zlast) * 10^(fr_profile(d)/20);
|
|
|
|
|
|
|
+ A2BF(d, c, b) = limit_a2b * (Z(d)/((lower_z(c, b) + upper_z(c, b)) / 2)) * 10^(fr_profile(d)/20);
|
|
|
else
|
|
else
|
|
|
- A2BF(d, c, b) = vlast;
|
|
|
|
|
|
|
+ A2BF(d, c, b) = limit_a2b;
|
|
|
end
|
|
end
|
|
|
end
|
|
end
|
|
|
end
|
|
end
|
|
@@ -164,8 +221,8 @@ function [FIR] = sphear_fir_design(F, A2BF, n, maxboost, Fs, Fl, plot_on, W, X,
|
|
|
|
|
|
|
|
%% we fit the spline taking into account one additional point
|
|
%% we fit the spline taking into account one additional point
|
|
|
%% on the bottom edge so that it will fit better with the existing
|
|
%% on the bottom edge so that it will fit better with the existing
|
|
|
- %% low frequency points, now we need to find the exact transition
|
|
|
|
|
- %% frequency in the interpolated array
|
|
|
|
|
|
|
+ %% low frequency points, now we find the exact transition frequency
|
|
|
|
|
+ %% in the interpolated array
|
|
|
for idx = 1:size(fh(:))
|
|
for idx = 1:size(fh(:))
|
|
|
if (fh(idx) <= F(dlast)/(Fs/2))
|
|
if (fh(idx) <= F(dlast)/(Fs/2))
|
|
|
istart = idx;
|
|
istart = idx;
|
|
@@ -176,22 +233,21 @@ function [FIR] = sphear_fir_design(F, A2BF, n, maxboost, Fs, Fl, plot_on, W, X,
|
|
|
fl = [0 F(1:dlast-1)']' ./ (Fs/2);
|
|
fl = [0 F(1:dlast-1)']' ./ (Fs/2);
|
|
|
rl = [A2BF(1,c,b) A2BF(1:dlast-1,c,b)']';
|
|
rl = [A2BF(1,c,b) A2BF(1:dlast-1,c,b)']';
|
|
|
|
|
|
|
|
|
|
+%% fh = [F(dlast:end)' Fs/2]' ./ (Fs/2);
|
|
|
|
|
+%% rh = [A2BF(dlast:end,c,b)' A2BF(end,c,b)]';
|
|
|
|
|
+
|
|
|
%% concatenate low and high frequency arrays
|
|
%% concatenate low and high frequency arrays
|
|
|
fi = cat(1, fl, fh(istart:end));
|
|
fi = cat(1, fl, fh(istart:end));
|
|
|
ri = cat(1, rl, rh(istart:end));
|
|
ri = cat(1, rl, rh(istart:end));
|
|
|
|
|
|
|
|
- printf("low frequency boost %f, high frequency boost %f\n", 20*log10(ri(1)), 20*log10(ri(end)));
|
|
|
|
|
%% force a max boost at low and high frequencies
|
|
%% force a max boost at low and high frequencies
|
|
|
if (maxboost != 0)
|
|
if (maxboost != 0)
|
|
|
- if (abs(ri(1)) > 10^(maxboost/20))
|
|
|
|
|
- printf("limit low freq boost from %f to %f dB (%f)\n", 20*log10(ri(1)), maxboost, ri(1));
|
|
|
|
|
- ri(1) = sign(ri(1)) * 10^(maxboost/20);
|
|
|
|
|
- printf("new limit %f\n", ri(1));
|
|
|
|
|
- end
|
|
|
|
|
- if (abs(ri(end)) > 10^(maxboost/20))
|
|
|
|
|
- printf("limit high freq boost from %f dB to %f dB (%f)\n", 20*log10(ri(end)), maxboost, ri(end));
|
|
|
|
|
- ri(end) = sign(ri(end)) * 10^(maxboost/20);
|
|
|
|
|
- printf("new limit %f\n", ri(end));
|
|
|
|
|
|
|
+ printf("b-format component:%d capsule:%d \n", b, c);
|
|
|
|
|
+ for idx = 1:size(ri(:))
|
|
|
|
|
+ if (abs(ri(idx)) > 10^(maxboost/20))
|
|
|
|
|
+ printf("freq=%.2f: limit boost from %.2f to %.2f dB\n", fi(idx)*Fs/2, 10*log10(ri(idx)), maxboost);
|
|
|
|
|
+ ri(idx) = sign(ri(idx)) * 10^(maxboost/20);
|
|
|
|
|
+ end
|
|
|
end
|
|
end
|
|
|
end
|
|
end
|
|
|
|
|
|
|
@@ -224,18 +280,18 @@ function [FIR] = sphear_fir_design(F, A2BF, n, maxboost, Fs, Fl, plot_on, W, X,
|
|
|
%% title(sprintf("B format FIR filters (transition frequency: %d Hz)", Fl));
|
|
%% title(sprintf("B format FIR filters (transition frequency: %d Hz)", Fl));
|
|
|
hold on;
|
|
hold on;
|
|
|
%% plot real measured points
|
|
%% plot real measured points
|
|
|
- %% ph = semilogx(F, 20*log10(abs(A2BF(:,c,b)))', popts, "linewidth", 1.5);
|
|
|
|
|
|
|
+ %% ph = semilogx(F, 10*log10(abs(A2BF(:,c,b)))', popts, "linewidth", 1.5);
|
|
|
%% plot extrapolated filter
|
|
%% plot extrapolated filter
|
|
|
if (b == 1)
|
|
if (b == 1)
|
|
|
%% W
|
|
%% W
|
|
|
- wh = semilogx(fi*(Fs/2), 20*log10(abs(ri)), wopts, "linewidth", 1.5);
|
|
|
|
|
|
|
+ wh = semilogx(fi*(Fs/2), 10*log10(abs(ri)), wopts, "linewidth", 1.5);
|
|
|
else
|
|
else
|
|
|
if (b == 4)
|
|
if (b == 4)
|
|
|
%% Z
|
|
%% Z
|
|
|
- zh = semilogx(fi*(Fs/2), 20*log10(abs(ri)), zopts, "linewidth", 1.5);
|
|
|
|
|
|
|
+ zh = semilogx(fi*(Fs/2), 10*log10(abs(ri)), zopts, "linewidth", 1.5);
|
|
|
else
|
|
else
|
|
|
%% XY
|
|
%% XY
|
|
|
- xyh = semilogx(fi*(Fs/2), 20*log10(abs(ri)), xyopts, "linewidth", 1.5);
|
|
|
|
|
|
|
+ xyh = semilogx(fi*(Fs/2), 10*log10(abs(ri)), xyopts, "linewidth", 1.5);
|
|
|
end
|
|
end
|
|
|
end
|
|
end
|
|
|
end
|
|
end
|
|
@@ -245,7 +301,8 @@ function [FIR] = sphear_fir_design(F, A2BF, n, maxboost, Fs, Fl, plot_on, W, X,
|
|
|
%% "Two alternative minimum-phase filters tested perceptually"
|
|
%% "Two alternative minimum-phase filters tested perceptually"
|
|
|
%% Robert Mores and Ralf Hendrych, AES Convertion paper 9554
|
|
%% Robert Mores and Ralf Hendrych, AES Convertion paper 9554
|
|
|
%%
|
|
%%
|
|
|
- IR = fir2(n, fi, ri)';
|
|
|
|
|
|
|
+ IR = fir2(n - 1, fi, ri)';
|
|
|
|
|
+ %%
|
|
|
%% number of samples in the sin transition
|
|
%% number of samples in the sin transition
|
|
|
%%
|
|
%%
|
|
|
%% 8 seems to be the sweet spot for a FIR filter size of 256,
|
|
%% 8 seems to be the sweet spot for a FIR filter size of 256,
|
|
@@ -253,22 +310,26 @@ function [FIR] = sphear_fir_design(F, A2BF, n, maxboost, Fs, Fl, plot_on, W, X,
|
|
|
%% the flattness of the frequency response
|
|
%% the flattness of the frequency response
|
|
|
%%
|
|
%%
|
|
|
M = size(IR)(1);
|
|
M = size(IR)(1);
|
|
|
- N = (M-1)/32;
|
|
|
|
|
|
|
+ N = M/32;
|
|
|
R = 0.5 + (0.5 * sin(pi * ((1:(2*N)) - N) / (2 * N)));
|
|
R = 0.5 + (0.5 * sin(pi * ((1:(2*N)) - N) / (2 * N)));
|
|
|
- ramp = cat(1, zeros(M/2 - N + 1, 1), R', ones(M/2 - N, 1));
|
|
|
|
|
|
|
+ ramp = cat(1, zeros(M/2 - N, 1), R', ones(M/2 - N, 1));
|
|
|
IRMP = IR .* ramp * 2;
|
|
IRMP = IR .* ramp * 2;
|
|
|
-
|
|
|
|
|
|
|
+ %%
|
|
|
%% now we trim the filter and return one that is 1/2 the size requested
|
|
%% now we trim the filter and return one that is 1/2 the size requested
|
|
|
|
|
+ %% because it is now almost minimum phase and the first half is zeroed
|
|
|
|
|
+ %%
|
|
|
%% we also window the end of the impulse response... (unnecessary?)
|
|
%% we also window the end of the impulse response... (unnecessary?)
|
|
|
%% T = 4;
|
|
%% T = 4;
|
|
|
%% tapper = cat(1, ones(M/2 - 2*T + 1, 1), (1.0 - (0.5 + (0.5 * sin(pi * ((1:(2*T)) - T) / (2 * T)))))');
|
|
%% tapper = cat(1, ones(M/2 - 2*T + 1, 1), (1.0 - (0.5 + (0.5 * sin(pi * ((1:(2*T)) - T) / (2 * T)))))');
|
|
|
- FIR(b, c, :) = IRMP((M-1)/2 - N:(M-1)/2 - N + (M-1)/2);
|
|
|
|
|
|
|
+ %%
|
|
|
|
|
+ FIR(b, c, :) = IRMP(M/2 - N:M/2 - N + M/2 - 1);
|
|
|
end
|
|
end
|
|
|
end
|
|
end
|
|
|
|
|
|
|
|
%% write a tetraproc compatible wav file
|
|
%% write a tetraproc compatible wav file
|
|
|
fuma = [sqrt(2)/2, 1, 1, 1];
|
|
fuma = [sqrt(2)/2, 1, 1, 1];
|
|
|
for b = 1:4
|
|
for b = 1:4
|
|
|
|
|
+ %% each column is one channel, W/X/Y/Z, all capsules concatenated
|
|
|
TETRA(:, b) = fuma(b) * cat(1,
|
|
TETRA(:, b) = fuma(b) * cat(1,
|
|
|
reshape(FIR(b, 1, :), 1, [])',
|
|
reshape(FIR(b, 1, :), 1, [])',
|
|
|
reshape(FIR(b, 2, :), 1, [])',
|
|
reshape(FIR(b, 2, :), 1, [])',
|