1
0

sphear_fir_design.m 11 KB

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