1
0

sphear_fir_design.m 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. %%
  2. %% *SpHEAR Project
  3. %%
  4. %% sphear_fir_design: design a matrix of FIR filters that equalizes the microphone
  5. %%
  6. %% usage:
  7. %% [FIR] = sphear_fir_design(F, A2BF, n, Fs, Fl, plot_on, W, X, Y, Z)
  8. %%
  9. %% F: array of filter section center frequencies
  10. %% A2BF: vector of A2B matrixes, one for each frequency
  11. %% n: order of the FIR filter being created
  12. %% maxboost: max high and low frequency boost in dB of the final filter
  13. %% Fs: sampling rate
  14. %% Fl: frequency limit, below it use A2B matrixes, above it use W, X, Y, Z shapes
  15. %% plot_on: plot frequency response of components, default is "epsc"
  16. %% can be one of 0, "eps", "epsc" (color), "png", "pngc" (color)
  17. %%
  18. %% for the first run do not supply W/X/Y/Z, render the filters and then run
  19. %% sphear_fir_extract to extract the W, X, Y and Z filter shapes, finally run
  20. %% this again with the extra parameters below
  21. %%
  22. %% W: extracted shape of W filter (used above Fl)
  23. %% X: extracted shape of X filter (used above Fl)
  24. %% Y: extracted shape of Y filter (used above Fl)
  25. %% Z: extracted shape of Z filter (used above Fl)
  26. %% Z is optional, if not supplied X and Y are averaged to obtain Z
  27. %%
  28. %% Copyright 2016, Fernando Lopez-Lezcano, All Rights Reserved
  29. %% nando@ccrma.stanford.edu
  30. %%
  31. %% This program is free software: you can redistribute it and/or modify
  32. %% it under the terms of the GNU General Public License as published by
  33. %% the Free Software Foundation, either version 3 of the License, or
  34. %% (at your option) any later version.
  35. %%
  36. %% This program is distributed in the hope that it will be useful,
  37. %% but WITHOUT ANY WARRANTY; without even the implied warranty of
  38. %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  39. %% GNU General Public License for more details.
  40. %%
  41. %% You should have received a copy of the GNU General Public License
  42. %% along with this program. If not, see <http://www.gnu.org/licenses/>.
  43. function [FIR] = sphear_fir_design(F, A2BF, n, maxboost, Fs, Fl, plot_on, W, X, Y, Z)
  44. if (plot_on != 0)
  45. fig = figure;
  46. end
  47. if (exist("W") == 1 && ! exist("Z"))
  48. %% Z was not supplied, just average X and Y data
  49. Z = (X + Y) / 2;
  50. end
  51. %% find the index of the transition frequency band
  52. fl_idx = 1;
  53. for d = 1:size(A2BF(:,1,1))(1)
  54. if (F(d) <= Fl)
  55. fl_idx = d;
  56. end
  57. end
  58. printf("transition band is %d at %f Hz\n", fl_idx, F(fl_idx));
  59. %% create a frequency response profile in dB
  60. fr_profile = interp1([F(1), F(fl_idx), F(end)], [0, 0, 0], F);
  61. %% all b format components
  62. for b = 1:4
  63. %% all capsules
  64. for c = 1:4
  65. %% all frequency bands
  66. for d = 1:size(A2BF(:,1,1))(1)
  67. %% create an array of points to design the filter
  68. if (F(d) <= Fl)
  69. %% below the limit frequency we use the A2B coefficients
  70. vlast = A2BF(d, c, b);
  71. %% apply frequency response profile
  72. A2BF(d, c, b) = vlast * 10^(fr_profile(d)/20);
  73. dlast = d;
  74. if (exist("W") == 1)
  75. wlast = W(d);
  76. xlast = X(d);
  77. ylast = Y(d);
  78. zlast = Z(d);
  79. end
  80. else
  81. %% above the limit frequency we insert the W, X, Y and Z
  82. %% data if they exist, otherwise keep the last valid value
  83. %% from the A2B coefficients (ie: flat above the transition
  84. %% frequency
  85. if (b == 1)
  86. if (exist("W") == 1)
  87. A2BF(d, c, b) = vlast * (W(d)/wlast) * 10^(fr_profile(d)/20);
  88. else
  89. A2BF(d, c, b) = vlast;
  90. end
  91. end
  92. if (b == 2)
  93. if (exist("X") == 1)
  94. A2BF(d, c, b) = vlast * (X(d)/xlast) * 10^(fr_profile(d)/20);
  95. else
  96. A2BF(d, c, b) = vlast;
  97. end
  98. end
  99. if (b == 3)
  100. if (exist("Y") == 1)
  101. A2BF(d, c, b) = vlast * (Y(d)/ylast) * 10^(fr_profile(d)/20);
  102. else
  103. A2BF(d, c, b) = vlast;
  104. end
  105. end
  106. if (b == 4)
  107. if (exist("Z") == 1)
  108. A2BF(d, c, b) = vlast * (Z(d)/zlast) * 10^(fr_profile(d)/20);
  109. else
  110. A2BF(d, c, b) = vlast;
  111. end
  112. end
  113. end
  114. end
  115. end
  116. end
  117. %% normalize all components to the average of W levels for all
  118. %% capsules around the transition frequency
  119. if ((dlast+1) > size(A2BF(:,1,1))(1))
  120. %% special case for testing with a very high transition frequency
  121. avg = [dlast-3, dlast-2, dlast-1];
  122. else
  123. avg = [dlast-1, dlast, dlast+1];
  124. end
  125. nrm = mean([mean(A2BF(avg,1,1))
  126. mean(A2BF(avg,2,1))
  127. mean(A2BF(avg,3,1))
  128. mean(A2BF(avg,4,1))]);
  129. for b = 1:4
  130. for c = 1:4
  131. %% normalize coefficients
  132. for dn = 1:size(A2BF(:,1,1))(1)
  133. A2BF(dn,c,b) = A2BF(dn,c,b) ./ nrm;
  134. end
  135. %% fit a spline to the W, X, Y and Z data and extrapolate
  136. %% highest frequency if it does not exist in the measurements
  137. %%
  138. %% the 200Hz spacing of the points is arbitrary, we should
  139. %% select something that makes more sense
  140. %%
  141. fh = (F(dlast-1)/(Fs/2):200/(Fs/2):1)';
  142. %% make the end really 1.0
  143. fh(end) = 1;
  144. rh = interp1(F(dlast-1:end)/(Fs/2), A2BF(dlast-1:end,c,b)', fh, "spline", "extrap");
  145. %% we fit the spline taking into account one additional point
  146. %% on the bottom edge so that it will fit better with the existing
  147. %% low frequency points, now we need to find the exact transition
  148. %% frequency in the interpolated array
  149. for idx = 1:size(fh(:))
  150. if (fh(idx) <= F(dlast)/(Fs/2))
  151. istart = idx;
  152. end
  153. end
  154. %% extract the low frequency A2B coefficients
  155. fl = [0 F(1:dlast-1)']' ./ (Fs/2);
  156. rl = [A2BF(1,c,b) A2BF(1:dlast-1,c,b)']';
  157. %% concatenate low and high frequency arrays
  158. fi = cat(1, fl, fh(istart:end));
  159. ri = cat(1, rl, rh(istart:end));
  160. printf("low frequency boost %f, high frequency boost %f\n", 20*log10(ri(1)), 20*log10(ri(end)));
  161. %% force a max boost at low and high frequencies
  162. if (maxboost != 0)
  163. if (abs(ri(1)) > 10^(maxboost/20))
  164. printf("limit low freq boost from %f to %f dB (%f)\n", 20*log10(ri(1)), maxboost, ri(1));
  165. ri(1) = sign(ri(1)) * 10^(maxboost/20);
  166. printf("new limit %f\n", ri(1));
  167. end
  168. if (abs(ri(end)) > 10^(maxboost/20))
  169. printf("limit high freq boost from %f dB to %f dB (%f)\n", 20*log10(ri(end)), maxboost, ri(end));
  170. ri(end) = sign(ri(end)) * 10^(maxboost/20);
  171. printf("new limit %f\n", ri(end));
  172. end
  173. end
  174. if (plot_on != 0)
  175. if (strcmp(plot_on, "epsc") || strcmp(plot_on, "pngc"))
  176. %% color plot
  177. popts = '.k';
  178. wopts = 'b';
  179. xyopts = 'r';
  180. zopts = 'g';
  181. copt = '-color';
  182. cext = "_color";
  183. else
  184. %% mono plot
  185. popts = '.k';
  186. wopts = '-';
  187. xyopts = '--';
  188. zopts = ':';
  189. copt = '-mono';
  190. cext = "_mono";
  191. end
  192. grid on;
  193. axis([400, 20000]);
  194. xticks = [300, 500, 1000, 2000, 3000, 5000, 7000, 10000, 20000];
  195. set(gca, 'XTick', xticks);
  196. set(gca, 'XTickLabel', sprintf('%d|', xticks));
  197. ca=get (gcf, "currentaxes");
  198. set(ca,"fontweight","bold","linewidth",1.5)
  199. pbaspect([16, 9, 1])
  200. %% title(sprintf("B format FIR filters (transition frequency: %d Hz)", Fl));
  201. hold on;
  202. %% plot real measured points
  203. %% ph = semilogx(F, 20*log10(abs(A2BF(:,c,b)))', popts, "linewidth", 1.5);
  204. %% plot extrapolated filter
  205. if (b == 1)
  206. %% W
  207. wh = semilogx(fi*(Fs/2), 20*log10(abs(ri)), wopts, "linewidth", 1.5);
  208. else
  209. if (b == 4)
  210. %% Z
  211. zh = semilogx(fi*(Fs/2), 20*log10(abs(ri)), zopts, "linewidth", 1.5);
  212. else
  213. %% XY
  214. xyh = semilogx(fi*(Fs/2), 20*log10(abs(ri)), xyopts, "linewidth", 1.5);
  215. end
  216. end
  217. end
  218. %% design the filter and try to get it close to minimum phase
  219. %%
  220. %% "Two alternative minimum-phase filters tested perceptually"
  221. %% Robert Mores and Ralf Hendrych, AES Convertion paper 9554
  222. %%
  223. IR = fir2(n, fi, ri)';
  224. %% number of samples in the sin transition
  225. %%
  226. %% 8 seems to be the sweet spot for a FIR filter size of 256,
  227. %% 16 leads to bigger phase shift, 4 starts to impact negatively
  228. %% the flattness of the frequency response
  229. %%
  230. M = size(IR)(1);
  231. N = (M-1)/32;
  232. R = 0.5 + (0.5 * sin(pi * ((1:(2*N)) - N) / (2 * N)));
  233. ramp = cat(1, zeros(M/2 - N + 1, 1), R', ones(M/2 - N, 1));
  234. IRMP = IR .* ramp * 2;
  235. %% now we trim the filter and return one that is 1/2 the size requested
  236. %% we also window the end of the impulse response... (unnecessary?)
  237. %% T = 4;
  238. %% tapper = cat(1, ones(M/2 - 2*T + 1, 1), (1.0 - (0.5 + (0.5 * sin(pi * ((1:(2*T)) - T) / (2 * T)))))');
  239. FIR(b, c, :) = IRMP((M-1)/2 - N:(M-1)/2 - N + (M-1)/2);
  240. end
  241. end
  242. %% write a tetraproc compatible wav file
  243. fuma = [sqrt(2)/2, 1, 1, 1];
  244. for b = 1:4
  245. TETRA(:, b) = fuma(b) * cat(1,
  246. reshape(FIR(b, 1, :), 1, [])',
  247. reshape(FIR(b, 2, :), 1, [])',
  248. reshape(FIR(b, 3, :), 1, [])',
  249. reshape(FIR(b, 4, :), 1, [])');
  250. end
  251. wavwrite(TETRA, Fs, 'calibration/tetraproc_matrix.wav');
  252. if (plot_on != 0)
  253. l = legend([wh, zh, xyh], {'W', 'XY', 'Z'});
  254. legend(l, 'location', 'west');
  255. if (exist("W") == 1)
  256. if (strcmp(plot_on, 'eps') || strcmp(plot_on, 'epsc') || plot_on == 1)
  257. print(fig, '-deps','-tight',copt,["figures/wxyz_filter_ff" cext ".eps"]);
  258. else
  259. print(fig, '-dpng',copt,["figures/wxyz_filter_ff" cext ".png"]);
  260. end
  261. else
  262. if (strcmp(plot_on, 'eps') || strcmp(plot_on, 'epsc') || plot_on == 1)
  263. print(fig, '-deps','-tight',copt,["figures/wxyz_filter_lf" cext ".eps"]);
  264. else
  265. print(fig, '-dpng',copt,["figures/wxyz_filter_lf" cext ".png"]);
  266. end
  267. end
  268. end
  269. end