sphear_calibration_common.m 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852
  1. %% -*- mode: octave -*-
  2. %%
  3. %% *SpHEAR Project
  4. %%
  5. %% Common functions for A2B encoder design
  6. %%
  7. %% Copyright 2016-2017, Fernando Lopez-Lezcano, All Rights Reserved
  8. %% nando@ccrma.stanford.edu
  9. %%
  10. %% This program is free software: you can redistribute it and/or modify
  11. %% it under the terms of the GNU General Public License as published by
  12. %% the Free Software Foundation, either version 3 of the License, or
  13. %% (at your option) any later version.
  14. %%
  15. %% This program is distributed in the hope that it will be useful,
  16. %% but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. %% GNU General Public License for more details.
  19. %%
  20. %% You should have received a copy of the GNU General Public License
  21. %% along with this program. If not, see <http://www.gnu.org/licenses/>.
  22. %% make sure octave treats this as a script file
  23. 1;
  24. pkg load signal;
  25. %%
  26. %% Read an A-Format signal for specified measurements
  27. %%
  28. global DATA = "../data/";
  29. global CURRENT = "current/";
  30. function [AF, FS] = sphear_af_read(measurements, raw = false);
  31. global DATA;
  32. global CURRENT;
  33. if (raw)
  34. type = "";
  35. else
  36. type = "cal-";
  37. end
  38. if (exist([DATA CURRENT "af/a-format-cal-001-5.wav"]))
  39. %% assume it is an eight capsule microphone
  40. capnames = cellstr(['1'; '2'; '3'; '4'; '5'; '6'; '7'; '8']);
  41. capsules = 8;
  42. else
  43. %% otherwise a tetrahedral microphone
  44. capnames = cellstr(['LF'; 'RF'; 'LB'; 'RB']);
  45. capsules = 4;
  46. end
  47. %% read all measurements
  48. for m = measurements
  49. %% read all capsules
  50. for c = 1:capsules
  51. %% read in calibrated capsule measurement
  52. [A, FS] = wavread([DATA CURRENT "af/a-format-" type sprintf("%03d", m) "-" char(capnames(c)) ".wav"]);
  53. AF(m, c, :) = A;
  54. end
  55. end
  56. %% read any up/down measurements
  57. if (exist([DATA CURRENT "af/a-format-cal-up-001-5.wav"]))
  58. for m = measurements
  59. %% read all capsules
  60. for c = 1:capsules
  61. %% read in calibrated capsule measurement
  62. [A, FS] = wavread([DATA CURRENT "af/a-format-" type sprintf("up-%03d", m) "-" char(capnames(c)) ".wav"]);
  63. AF(m + 16, c, :) = A;
  64. end
  65. end
  66. end
  67. if (exist([DATA CURRENT "af/a-format-cal-down-001-5.wav"]))
  68. for m = measurements
  69. %% read all capsules
  70. for c = 1:capsules
  71. %% read in calibrated capsule measurement
  72. [A, FS] = wavread([DATA CURRENT "af/a-format-" type sprintf("down-%03d", m) "-" char(capnames(c)) ".wav"]);
  73. AF(m + 16 + 16, c, :) = A;
  74. end
  75. end
  76. end
  77. end
  78. function [AF, FS] = sphear_af_read_capsule(measurements, raw = false);
  79. global DATA;
  80. global CURRENT;
  81. if (raw)
  82. type = "";
  83. else
  84. type = "cal-";
  85. end
  86. if (exist([DATA CURRENT "af/capsule_u-cal-001-1.wav"]))
  87. %% assume it is an eight capsule microphone
  88. capnames = cellstr(['1'; '2'; '3'; '4'; '5'; '6'; '7'; '8']);
  89. capsules = 1;
  90. else
  91. %% otherwise a tetrahedral microphone
  92. capnames = cellstr(['LF'; 'RF'; 'LB'; 'RB']);
  93. capsules = 4;
  94. end
  95. %% read all measurements
  96. for m = measurements
  97. %% read all capsules
  98. for c = 1:capsules
  99. %% read in calibrated capsule measurement
  100. printf("m:%d -> c:%d\n", m, c);
  101. [A, FS] = wavread([DATA CURRENT "af/capsule_u-" type sprintf("%03d", m) "-" char(capnames(c)) ".wav"]);
  102. AF(m, c, :) = A;
  103. end
  104. end
  105. end
  106. %%
  107. %% Calculate power for signal in a frequency range
  108. %%
  109. %% cardioid == 1: replace Z with a cardioid pointing at horizontal angle "azim"
  110. %% cardioid == 2: replace Z with a second order lobe pointing at horizontal angle "azim"
  111. function [R] = sphear_signal_power(SIG, FS, fmin, fmax, fft_size, cardioid = 0, azim = 45)
  112. %% indexes in array for frequency bins
  113. imin = round((fft_size/2) * fmin / (FS/2));
  114. imax = round((fft_size/2) * fmax / (FS/2));
  115. measurements = size(SIG)(1);
  116. signals = size(SIG,2);
  117. %% calculate power in frequency range
  118. for m = (1:measurements)
  119. for s = (1:signals)
  120. if (s == 4 && cardioid > 0)
  121. if (cardioid < 2)
  122. %% replace Z with a cardioid
  123. spectrum = fft((SIG(m, 1, :) +
  124. 1.0 * SIG(m, 2, :) * cos(azim/180*pi) +
  125. 1.0 * SIG(m, 3, :) * sin(azim/180*pi)) / 2, fft_size);
  126. else
  127. %% replace Z with a second order lobe
  128. spectrum = fft((SIG(m, 1, :) +
  129. (1.0 * SIG(m, 2, :) * cos(azim/180*pi)) +
  130. (1.0 * SIG(m, 3, :) * sin(azim/180*pi)) +
  131. (1.0 * SIG(m, 5, :) * cos((2 * azim)/180*pi)) +
  132. (1.0 * SIG(m, 6, :) * sin((2 * azim)/180*pi))) / 2.5, fft_size);
  133. end
  134. else
  135. spectrum = fft(SIG(m, s, :), fft_size);
  136. end
  137. %% return total power of signal in frequency range
  138. R(m,s,:) = sqrt(sum(spectrum(imin:imax).*conj(spectrum(imin:imax)))/(imax - imin));
  139. end
  140. end
  141. end
  142. %%
  143. %% Calculate power for capsule in a frequency range
  144. %%
  145. function [R] = sphear_capsule_power(SIG, FS, fmin, fmax, fft_size)
  146. %% indexes in array for frequency bins
  147. imin = round((fft_size/2) * fmin / (FS/2));
  148. imax = round((fft_size/2) * fmax / (FS/2));
  149. measurements = size(SIG)(1);
  150. signals = size(SIG,2);
  151. %% calculate power in frequency range
  152. for m = (1:measurements)
  153. for s = (1:signals)
  154. spectrum = fft(SIG(m, s, :), fft_size);
  155. %% return total power of signal in frequency range
  156. R(m,s,:) = sqrt(sum(spectrum(imin:imax).*conj(spectrum(imin:imax)))/(imax - imin));
  157. end
  158. end
  159. end
  160. %%
  161. %% Calculate A to B matrix from matrix of power measurements
  162. %%
  163. function [A2B PV COND ERROR] = sphear_calculate_a2b(R, M)
  164. if (exist("M"))
  165. [A2B PV ERROR] = A2B_matrix(R, M);
  166. else
  167. [A2B PV ERROR] = A2B_matrix(R);
  168. end
  169. %% normalize matrix
  170. %%
  171. %% normalize first order components of the matrix first
  172. A2B = A2B ./ mean(mean(abs(A2B(:,1:4))));
  173. if (size(A2B, 2) > 4)
  174. %% S/T/U/V are difference microphones, so at the reference
  175. %% frequency range their signal is much lower than the
  176. %% first order components, so normalize separately
  177. ST_avg = mean(mean(abs(A2B(:,5:6))));
  178. if (ST_avg != 0)
  179. A2B(:,5:6) = A2B(:,5:6) ./ ST_avg;
  180. end
  181. UV_avg = mean(mean(abs(A2B(:,7:8))));
  182. if (UV_avg != 0)
  183. A2B(:,7:8) = A2B(:,7:8) ./ UV_avg;
  184. end
  185. end
  186. %% return condition number
  187. COND = cond(A2B);
  188. end
  189. %% Render a calibrated capsule impulse response through an A2B matrix
  190. %%
  191. %% m: number of measurement to render
  192. %% A2B: A to B matrix
  193. %% FIR: optional FIR filters
  194. %% fft_size: FFT size for filtering
  195. function [BF, BFF] = sphear_af_to_bf(AF, A2B, FIR, fft_size);
  196. measurements = size(AF)(1);
  197. BF = zeros(measurements, size(A2B, 2), size(AF, 3));
  198. for m = (1:measurements)
  199. for c = (1:size(AF,2))
  200. bfc(1,:,:) = reshape(AF(m,c,:), 1, []) .* reshape(A2B(c,:), 1, [])';
  201. BF(m,:,:) = BF(m,:,:) .+ bfc(1,:,:);
  202. end
  203. end
  204. if exist("FIR")
  205. if (size(FIR, 1) <= 8)
  206. %% filter the B format signal through the common FIR filters
  207. for m = (1:measurements)
  208. for b = 1:size(FIR, 1)
  209. %% we add trailing zeros to let the filter ring
  210. BFF(m,b,:) = fftfilt(reshape(FIR(b,:), 1, [])', cat(1, reshape(BF(m,b,:), 1, [])', zeros(2*size(FIR(1,:))(2),1))', fft_size);
  211. end
  212. end
  213. else
  214. %% first four components use common filters, U/V use filter arrays
  215. for m = (1:measurements)
  216. for b = 1:4
  217. BFF(m,b,:) = fftfilt(reshape(FIR(b,:), 1, [])', cat(1, reshape(BF(m,b,:), 1, [])', zeros(2*size(FIR(1,:))(2),1))', fft_size);
  218. end
  219. end
  220. capsules = 8;
  221. for m = (1:measurements)
  222. for b = 5:6
  223. for c = 1:capsules
  224. fi = 4 + (b - 5)*capsules + c;
  225. if (c == 1)
  226. BFF(m,b,:) = fftfilt(reshape(FIR(fi,:), 1, [])', cat(1, reshape(BF(m,b,:), 1, [])', zeros(2*size(FIR(1,:))(2),1))', fft_size);
  227. else
  228. BFF(m,b,:) = reshape(BFF(m,b,:), size(BFF, 3), 1)' .+ fftfilt(reshape(FIR(fi,:), 1, [])', cat(1, reshape(BF(m,b,:), 1, [])', zeros(2*size(FIR(1,:))(2),1))', fft_size);
  229. end
  230. end
  231. end
  232. end
  233. end
  234. else
  235. BFF = BF;
  236. end
  237. end
  238. %% Render a calibrated capsule impulse response through UV matrix filters
  239. %%
  240. %% m: number of measurement to render
  241. %% A2B: A to B matrix
  242. %% FIR: optional FIR filters
  243. %% fft_size: FFT size for filtering
  244. function [UV, UVF] = sphear_af_to_bf_new(AF, A2B, FIR, fft_size);
  245. measurements = size(AF)(1);
  246. BF = zeros(measurements, size(A2B, 2), size(AF, 3));
  247. for m = (1:measurements)
  248. for c = (1:size(AF,2))
  249. bfc(1,:,:) = reshape(AF(m,c,:), 1, []) .* reshape(A2B(c,:), 1, [])';
  250. BF(m,:,:) = BF(m,:,:) .+ bfc(1,:,:);
  251. end
  252. end
  253. if exist("FIR")
  254. if (size(FIR, 1) <= 6)
  255. %% filter the B format signal through four or six FIR filters
  256. for m = (1:measurements)
  257. for b = 1:size(FIR, 1)
  258. %% we add trailing zeros to let the filter ring
  259. BFF(m,b,:) = fftfilt(reshape(FIR(b,:), 1, [])', cat(1, reshape(BF(m,b,:), 1, [])', zeros(2*size(FIR(1,:))(2),1))', fft_size);
  260. end
  261. end
  262. else
  263. %% filter the B format signal through single filters for first order
  264. %% and array of filters for second order
  265. end
  266. else
  267. BFF = BF;
  268. end
  269. end
  270. %%
  271. %% Calculate B format power in logarithmic frequency bands
  272. %%
  273. function [SP, FC, FE] = sphear_signal_power_all(SIG, FS, fmin, fmax, frefmin, frefmax, Noct, fft_size)
  274. %% calculate number of frequency bands spaced Noct
  275. n = floor(log(fmax/fmin)/log(2^(1/Noct)));
  276. %% adjust width of band so they fit exactly in frequency range
  277. N = log(2)/(log(fmax/fmin)/n);
  278. printf("%d frequency bands, %f bands per octave\n", n, N);
  279. %% first center frequency
  280. fcmin = fmin * 2^(1/(N * 2));
  281. %% all center and edge frequencies
  282. FC = fcmin * 2.^((0:n - 1)/N)';
  283. FE = fmin * 2.^((0:n)/N)';
  284. %% normalize power with respect to reference frequency band
  285. %% (measurement,signal,power)
  286. ref = sphear_signal_power(SIG, FS, frefmin, frefmax, fft_size);
  287. norm = mean(ref(:,:));
  288. %% we only normalize with respect to WXYZ, STUV in the frequency range
  289. %% where WXYZ are well behaved has low power (high pass filter due
  290. %% to the difference microphones) so we don't want to average that.
  291. %% If we do that the A2B coefficients are much higher than the ones
  292. %% for WXYZ and we get clipping for signals close to full scale (and
  293. %% then attenuation in the filtering stage after that)
  294. WXYZnorm = mean(ref(:,[1,2,3,4]));
  295. XYavg = mean(WXYZnorm([2,3]));
  296. %% use XY average for STUV
  297. norm = cat(1, WXYZnorm', [XYavg], [XYavg], [XYavg], [XYavg])';
  298. for i = (1:size(FC))
  299. %% (measurement,signal,band,power)
  300. SP(:,:,i) = sphear_signal_power(SIG, FS, FE(i), FE(i + 1), fft_size) ./ norm;
  301. end
  302. end
  303. %%
  304. %% Extract average shapes of B format filters
  305. %%
  306. %% measurements:
  307. %% 0 -> use defaults
  308. %% 1 -> use all measurements
  309. %% 2 -> use cardinal measurements
  310. %% 3 -> use diagonal measurements
  311. function [W X Y Z S T U V] = sphear_bf_extract_filter_shapes(B, measurements)
  312. %% cardinal and diagonal directions for four capsule array
  313. TETRA_CARDINALS = [1, 5, 9, 13];
  314. TETRA_DIAGONALS = [3, 7, 11, 15];
  315. %% cardinal and diagonal directions for eight capsule array
  316. OCTA_CARDINALS = [1, 3, 5, 7, 9, 11, 13, 15];
  317. OCTA_DIAGONALS = [2, 4, 6, 8, 10, 12, 14, 16];
  318. %% best directions for octa WXY
  319. OCTA_FLAT = OCTA_DIAGONALS;
  320. OCTA_BRIGHT = OCTA_CARDINALS;
  321. %% best directions for octa Z
  322. OCTA_FLAT_Z = [OCTA_FLAT + 16, OCTA_FLAT + 32];
  323. %% best directions for octa UV
  324. U_DIRS = [1, 5, 9, 13];
  325. V_DIRS = [3, 7, 11, 15];
  326. %% best directions for octa ST (up and down layers)
  327. S_DIRS = [U_DIRS + 16, U_DIRS + 32];
  328. T_DIRS = [V_DIRS + 16, V_DIRS + 32];
  329. %% all directions
  330. ALL_DIRS = [1:16];
  331. if (measurements == 0)
  332. %% cardinal directions
  333. if (size(B, 2) > 4)
  334. %% 8 capsules
  335. %% averaging all measurements for W seems to do better
  336. %%W_m = [OCTA_FLAT, OCTA_FLAT + 16, OCTA_FLAT + 32];
  337. %%Y_m = [OCTA_FLAT, OCTA_FLAT + 16, OCTA_FLAT + 32];
  338. %%X_m = [OCTA_FLAT, OCTA_FLAT + 16, OCTA_FLAT + 32];
  339. W_m = OCTA_FLAT;
  340. Y_m = OCTA_FLAT;
  341. X_m = OCTA_FLAT;
  342. if (size(B, 1) > 16)
  343. Z_m = OCTA_FLAT_Z;
  344. else
  345. Z_m = W_m;
  346. end
  347. S_m = S_DIRS;
  348. T_m = T_DIRS;
  349. U_m = U_DIRS;
  350. V_m = V_DIRS;
  351. else
  352. %% four capsules
  353. W_m = TETRA_CARDINALS;
  354. X_m = TETRA_CARDINALS;
  355. Y_m = TETRA_CARDINALS;
  356. Z_m = W_m;
  357. end
  358. else
  359. if (measurements == 1)
  360. %% diagonal directions
  361. if (size(B, 2) > 4)
  362. %% 8 capsules
  363. W_m = OCTA_BRIGHT;
  364. Y_m = OCTA_BRIGHT;
  365. X_m = OCTA_BRIGHT;
  366. Z_m = W_m;
  367. U_m = U_DIRS;
  368. V_m = V_DIRS;
  369. else
  370. %% four capsules
  371. W_m = TETRA_DIAGONALS;
  372. X_m = TETRA_DIAGONALS;
  373. Y_m = TETRA_DIAGONALS;
  374. Z_m = W_m;
  375. end
  376. else
  377. if (measurements == 2)
  378. %% average all measurements for all components
  379. W_m = ALL_DIRS;
  380. X_m = ALL_DIRS;
  381. Y_m = ALL_DIRS;
  382. Z_m = ALL_DIRS;
  383. U_m = ALL_DIRS;
  384. V_m = ALL_DIRS;
  385. end
  386. end
  387. end
  388. %% extract average response shape for W
  389. W = 1.0 ./ reshape(mean(B(W_m,1,:)), size(B(:,:,:))(3), 1);
  390. %% extract average response shape for XYZ
  391. X = 1.0 ./ reshape(mean(B(X_m,2,:)), size(B(:,:,:))(3), 1);
  392. Y = 1.0 ./ reshape(mean(B(Y_m,3,:)), size(B(:,:,:))(3), 1);
  393. Z = 1.0 ./ reshape(mean(B(Z_m,4,:)), size(B(:,:,:))(3), 1);
  394. if (size(B, 2) > 4)
  395. if (size(B, 1) > 16)
  396. %% full 3d measurements
  397. S = 1.0 ./ reshape(mean(B(S_m,5,:)), size(B(:,:,:))(3), 1);
  398. T = 1.0 ./ reshape(mean(B(T_m,6,:)), size(B(:,:,:))(3), 1);
  399. else
  400. %% flat 2d measurements, no S/T filters (ie: flat 1.0 filter)
  401. S = 1.0 ./ ones(size(B, 3), 1);
  402. T = 1.0 ./ ones(size(B, 3), 1);
  403. end
  404. U = 1.0 ./ reshape(mean(B(U_m,7,:)), size(B(:,:,:))(3), 1);
  405. V = 1.0 ./ reshape(mean(B(V_m,8,:)), size(B(:,:,:))(3), 1);
  406. end
  407. end
  408. %%
  409. %% Extract average shapes of capsule filters
  410. %%
  411. %% (using for now only the horizontal plane measurements)
  412. function [SHAPE] = sphear_extract_capsule_filter_shapes(A)
  413. %% for each capsule
  414. for c = (1:size(A,2))
  415. %% get indexes of measurements to average for each capsule
  416. mlll = mod(16 - (c * 2) + 0, 16) + 1;
  417. mll = mod(16 - (c * 2) + 1, 16) + 1;
  418. ml = mod(16 - (c * 2) + 2, 16) + 1;
  419. m = mod(16 - (c * 2) + 3, 16) + 1;
  420. mr = mod(16 - (c * 2) + 4, 16) + 1;
  421. mrr = mod(16 - (c * 2) + 5, 16) + 1;
  422. mrrr = mod(16 - (c * 2) + 6, 16) + 1;
  423. for n = (1:5)
  424. mm(n,:) = mod(16 - (c * 2) - n + 2, 16) + 1;
  425. mm(n + 5,:) = mod(16 - (c * 2) + n + 2, 16) + 1;
  426. end
  427. printf("c:%d -> %d %d %d %d %d %d %d\n", c, mlll, mll, ml, m, mr, mrr, mrrr);
  428. SHAPE(c,:) = (1.0 ./ reshape(mean(A([mll,ml,m,mr,mrr],c,:)), size(A,3), 1));
  429. %% average all directions
  430. %% SHAPE(c,:) = (1.0 ./ reshape(mean(A(:,c,:)), size(A,3), 1));
  431. end
  432. end
  433. %%
  434. %% Filter signals through capsule correction filters
  435. %%
  436. function [AFF] = sphear_filter_capsule_signals(AF, FIR, fft_size);
  437. measurements = size(AF, 1);
  438. capsules = size(AF, 2);
  439. samples = size(AF,3);
  440. AFF = zeros(measurements, capsules, samples);
  441. for m = (1:measurements)
  442. for c = (1:capsules)
  443. %% we add trailing zeros to let the filter ring
  444. AFF(m,c,:) = fftfilt(reshape(FIR(c,:), 1, [])', cat(1, reshape(AF(m,c,:), 1, [])', zeros(2*size(FIR(1,:))(2),1))', fft_size)(1:samples);
  445. end
  446. end
  447. end
  448. %%
  449. %% Calculate average power for each B format component and normalize A2B matrix
  450. %%
  451. function [A2B XY_gain, S_gain, T_gain, U_gain, V_gain, B_power] = sphear_bf_normalize_gain(A2B, B, FS, fmin, fmax, fft_size)
  452. do_Z = false;
  453. if (size(B,1)/3 > 8)
  454. %% we assume we have three sets of measurements and can do Z averaging
  455. do_Z = true;
  456. end
  457. %% (measurement,signal,band,power)
  458. B_power = sphear_signal_power(B, FS, fmin, fmax, fft_size);
  459. %% W, all measurements
  460. W_avg = mean(B_power(:,1));
  461. %% XY, peak of first order lobes
  462. XY_avg = mean([mean(B_power([1,9], 2)), mean(B_power([5, 13], 3))]);
  463. XY_gain = XY_avg/W_avg;
  464. if (do_Z)
  465. %% Z, +-elev weighted by elevation angle
  466. %% FIXME: we just hardwire the elevation angle for now
  467. Z_avg = mean(B_power([17:32,33:48],4)) / sin(43/180*pi);
  468. Z_gain = Z_avg/W_avg;
  469. else
  470. Z_gain = 0.0;
  471. end
  472. if (size(A2B, 2) > 4)
  473. %% UV, peak of second order lobes
  474. U_avg = mean(mean(B_power([1,5,9,13], 7)));
  475. U_gain = U_avg/W_avg;
  476. V_avg = mean(mean(B_power([3,7,11,15], 8)));
  477. V_gain = V_avg/W_avg;
  478. if (abs(mean(mean(A2B(:,[5,6])))) > 0)
  479. %% calculate peak value of s/t at measured elevation
  480. el = 43.0;
  481. s_t_val = 1.0 * sin((2*el)/180*pi);
  482. %% ST, peaks of lobes at +-elevation
  483. S_avg = mean([mean(B_power([1,9] + 16, 5)), mean(B_power([1,9] + 32, 5))]);
  484. S_gain = (S_avg/W_avg) * s_t_val;
  485. T_avg = mean([mean(B_power([5,13] + 16, 6)), mean(B_power([5, 13] + 32, 6))]);
  486. T_gain = (T_avg/W_avg) * s_t_val;
  487. else
  488. S_gain = 0.0;
  489. T_gain = 0.0;
  490. end
  491. else
  492. U_gain = 0.0;
  493. V_gain = 0.0;
  494. end
  495. %% normalize the A2B matrix
  496. if (do_Z)
  497. A2B(:,[2,3]) = A2B(:,[2,3]) / XY_gain;
  498. A2B(:,[4]) = A2B(:,[4]) / Z_gain;
  499. else
  500. A2B(:,[2,3,4]) = A2B(:,[2,3,4]) / XY_gain;
  501. end
  502. if (size(A2B, 2) > 4)
  503. if (abs(mean(mean(A2B(:,[5,6])))) > 0)
  504. printf("correct ST %f %f\n", S_gain, T_gain);
  505. A2B(:,[5]) = A2B(:,[5]) / S_gain;
  506. A2B(:,[6]) = A2B(:,[6]) / T_gain;
  507. end
  508. A2B(:,[7]) = A2B(:,[7]) / U_gain;
  509. A2B(:,[8]) = A2B(:,[8]) / V_gain;
  510. else
  511. end
  512. %% we want the W contribution of each capsule to be 1/8,
  513. %% so scale the whole matrix relative to mean W
  514. A2B(:,:) = A2B(:,:) / (mean(A2B(:, 1)) * 8);
  515. %% print gain scaling coefficients
  516. printf("XY gain: %f, Z gain: %f, ST: %f|%f, UV: %f|%f\n", XY_gain, Z_gain, S_gain, T_gain, U_gain, V_gain);
  517. end
  518. %%
  519. %% Calculate A2B coefficients in logarithmic frequency bands
  520. %%
  521. function [A2BF] = sphear_calculate_a2b_all(SIG, FS, fmin, fmax, frefmin, frefmax, Noct, fft_size)
  522. %% calculate number of frequency bands spaced Noct
  523. n = floor(log(fmax/fmin)/log(2^(1/Noct)));
  524. %% adjust width of band so they fit exactly in frequency range
  525. N = log(2)/(log(fmax/fmin)/n);
  526. printf("%d frequency bands, %f bands per octave\n", n, N);
  527. %% first center frequency
  528. fcmin = fmin * 2^(1/(N * 2));
  529. %% all center and edge frequencies
  530. FC = fcmin * 2.^((0:n - 1)/N)';
  531. FE = fmin * 2.^((0:n)/N)';
  532. band = 1;
  533. for i = (1:size(FC))
  534. %% get power in band
  535. R = sphear_signal_power(SIG, FS, FE(i), FE(i + 1), fft_size);
  536. %% calculate a2b matrix in band
  537. [a2b, pv, cond] = sphear_calculate_a2b(R);
  538. A2BF(band,:,:) = a2b;
  539. band = band + 1;
  540. end
  541. end
  542. %%
  543. %% Plot coefficients of A2B matrices as a function of frequency
  544. %%
  545. function sphear_plot_a2b_all(FC, A2B, fmin, fmax)
  546. hold on;
  547. grid on;
  548. axis([fmin, fmax]);
  549. xticks = [300, 500, 1000, 2000, 3000, 5000, 7000, 10000, 15000, 20000];
  550. set(gca, 'XTick', xticks);
  551. set(gca, 'XTickLabel', sprintf('%d|', xticks));
  552. ca=get (gcf, "currentaxes");
  553. set(ca,"fontweight","bold","linewidth",1.5)
  554. cmap = jet(size(A2B, 2));
  555. %% find the max frequency bin
  556. ifmin = 0;
  557. ifmax = 0;
  558. for i = (1:size(A2B, 1))
  559. if (FC(i) <= fmax)
  560. ifmax = i;
  561. end
  562. end
  563. ifmax
  564. FC(ifmax)
  565. size(A2B, 1)
  566. %% plot
  567. semilogx(FC([1:ifmax]), 10*log10(abs(A2B([1:ifmax],:,:))), ".-", "linewidth", 1.5);
  568. l = legend("C1", "C2", "C3", "C4", "C5", "C6", "C7", "C8");
  569. %%legend(l, 'location', 'west', 'outside');
  570. hold off;
  571. end
  572. %%%
  573. %%% Merge 2nd order low frequency filter shape with overall filter shape
  574. %%%
  575. function [UC, VC] = sphear_merge_filters(FC, FE, U, V, A2BF, fmax)
  576. %% find max frequency index
  577. imax = 0;
  578. for f = 1:size(FC, 1)
  579. if (FC(f) < fmax)
  580. imax = f;
  581. end
  582. end
  583. %% merge filters below the maximum frequency
  584. for f = 1:size(FC, 1)
  585. if (FC(f) < fmax)
  586. for c = 1:size(A2BF, 2)
  587. %%UC(f,c,:) = U(f) + real(10 * log10(abs(A2BF(f,c,5) / A2BF(imax,c,5))));
  588. %%VC(f,c,:) = V(f) + real(10 * log10(abs(A2BF(f,c,6) / A2BF(imax,c,6))));
  589. UC(f,c,:) = U(f) + real(10 * log10(abs(A2BF(imax,c,5) / A2BF(f,c,5))));
  590. VC(f,c,:) = V(f) + real(10 * log10(A2BF(imax,c,6) / abs(A2BF(f,c,6))));
  591. end
  592. else
  593. for c = 1:size(A2BF, 2)
  594. UC(f,c,:) = U(f);
  595. VC(f,c,:) = V(f);
  596. end
  597. end
  598. end
  599. end
  600. %%%
  601. %%% Plot component filters
  602. %%%
  603. function sphear_plot_all_filters(FC, UC, fmin, fmax)
  604. hold on;
  605. grid on;
  606. axis([fmin, fmax]);
  607. xticks = [300, 500, 1000, 2000, 3000, 5000, 7000, 10000, 15000, 20000];
  608. set(gca, 'XTick', xticks);
  609. set(gca, 'XTickLabel', sprintf('%d|', xticks));
  610. ca=get (gcf, "currentaxes");
  611. set(ca,"fontweight","bold","linewidth",1.5)
  612. %% find the max frequency bin
  613. ifmin = 0;
  614. ifmax = 0;
  615. for i = (1:size(UC, 1))
  616. if (FC(i) <= fmax)
  617. ifmax = i;
  618. end
  619. end
  620. %% plot
  621. semilogx(FC([1:ifmax]), UC([1:ifmax],:,:), ".-", "linewidth", 1.5);
  622. l = legend("C1", "C2", "C3", "C4", "C5", "C6", "C7", "C8");
  623. %%legend(l, 'location', 'west', 'outside');
  624. hold off;
  625. end
  626. %%%
  627. %%% Plot B format signal for three elevation measurement angles
  628. %%%
  629. function sphear_plot_measurements_3d(SIG, FC, FE, FS, N, freq)
  630. figure;
  631. hold on;
  632. axis([1, 16]);
  633. grid on;
  634. ca=get (gcf, "currentaxes");
  635. set(ca,"fontweight","bold","linewidth",1.5)
  636. %% find the approximate desired frequency band
  637. for f = [1:size(FC)]
  638. if (FC(f) <= freq)
  639. band = f;
  640. end
  641. end
  642. title(["B format power in frequency band (" sprintf("%.0f", FE(band)) " to " sprintf("%.0f", FE(band+1)) " Hz)"]);
  643. %% and plot the powers
  644. for i = [1,17,33]
  645. if (i < 17)
  646. wstyle = "ko-;W0;";
  647. color = "r";
  648. Xname = "X 0elev";
  649. Yname = "Y 0elev";
  650. else
  651. if (i < 33)
  652. wstyle = "ko--;WU;";
  653. color = "b";
  654. Xname = "X +elev";
  655. Yname = "Y +elev";
  656. else
  657. wstyle = "ko:;WD;";
  658. color = "g";
  659. Xname = "X -elev";
  660. Yname = "Y -elev";
  661. end
  662. end
  663. %% W
  664. plot(SIG((i:i+15),1,band), wstyle, "linewidth", 1.5);
  665. %% X/Y
  666. plot(SIG((i:i+15),[2],band), "color", color, sprintf("o-;%s;", Xname), "linewidth", 1.5);
  667. plot(SIG((i:i+15),[3],band), "color", color, sprintf("o-;%s;", Yname), "linewidth", 1.5);
  668. %% Z
  669. pZ = plot(SIG((i:i+15),4,band), "co-;Z;", "linewidth", 1.5);
  670. end
  671. legend('location', 'southwestoutside');
  672. hold off;
  673. end
  674. %%%
  675. %%% create X matrix for given elevation angles
  676. %%%
  677. function [M x y z s t u v az el] = sphear_X_matrix(n, up, down)
  678. eps = 0.01;
  679. %% create arrays of angles
  680. az = (0:360/n:360-eps)';
  681. %% map zero elevation into spherical harmonic components
  682. el = 0;
  683. for i = 1:size(az)
  684. x(i) = cos(az(i)/180*pi) * cos(el/180*pi);
  685. y(i) = sin(az(i)/180*pi) * cos(el/180*pi);
  686. z(i) = sin(el/180*pi);
  687. s(i) = cos(az(i)/180*pi) * sin((2*el)/180*pi);
  688. t(i) = sin(az(i)/180*pi) * sin((2*el)/180*pi);
  689. u(i) = cos((2*az(i))/180*pi) * cos(el/180*pi) * cos(el/180*pi);
  690. v(i) = sin((2*az(i))/180*pi) * cos(el/180*pi) * cos(el/180*pi);
  691. end
  692. %% map positive elevation measurements
  693. el = up;
  694. for i = 1:size(az)
  695. x(i + 16) = cos(az(i)/180*pi) * cos(el/180*pi);
  696. y(i + 16) = sin(az(i)/180*pi) * cos(el/180*pi);
  697. z(i + 16) = sin(el/180*pi);
  698. s(i + 16) = cos(az(i)/180*pi) * sin((2*el)/180*pi);
  699. t(i + 16) = sin(az(i)/180*pi) * sin((2*el)/180*pi);
  700. u(i + 16) = cos((2*az(i))/180*pi) * cos(el/180*pi) * cos(el/180*pi);
  701. v(i + 16) = sin((2*az(i))/180*pi) * cos(el/180*pi) * cos(el/180*pi);
  702. end
  703. %% map negative elevation measurements
  704. el = down;
  705. for i = 1:size(az)
  706. x(i + 32) = cos(az(i)/180*pi) * cos(el/180*pi);
  707. y(i + 32) = sin(az(i)/180*pi) * cos(el/180*pi);
  708. z(i + 32) = sin(el/180*pi);
  709. s(i + 32) = cos(az(i)/180*pi) * sin((2*el)/180*pi);
  710. t(i + 32) = sin(az(i)/180*pi) * sin((2*el)/180*pi);
  711. u(i + 32) = cos((2*az(i))/180*pi) * cos(el/180*pi) * cos(el/180*pi);
  712. v(i + 32) = sin((2*az(i))/180*pi) * cos(el/180*pi) * cos(el/180*pi);
  713. end
  714. %% return big matrix of projections
  715. M = [x' y' z' s' t' u' v'];
  716. end
  717. %%%
  718. %%% Fractional delay
  719. %%%
  720. %%% based on:
  721. %%% http://www.labbookpages.co.uk/audio/beamforming/fractionalDelay.html
  722. %%%
  723. %%% Also:
  724. %%% http://users.spa.aalto.fi/vpv/publications/vesan_vaitos/ch3_pt1_fir.pdf
  725. %%%
  726. %%% N = length of sinc function (should be odd)
  727. function [SINC] = sphear_delayed_sinc(N, delay)
  728. center = floor(N / 2);
  729. for t = (1:N)
  730. x = (t - 1) - delay;
  731. window = 0.54 - 0.46 * cos(2.0 * pi * (x + 0.5) / N);
  732. SINC(t) = sinc(x - center) * window;
  733. end
  734. end
  735. function [DSIG FIR] = sphear_delay_signal(SIG, delay, N, fft_size)
  736. FIR = sphear_delayed_sinc(N, delay);
  737. for m = [1:size(SIG, 1)]
  738. for c = [1:size(SIG, 2)]
  739. %% we add trailing zeros to let the filter ring
  740. DSIG(m, c, :) = fftfilt(FIR, cat(1, reshape(SIG(m, c, :), 1, [])', zeros(2*size(FIR, 2), 1))', fft_size)(round(N/2):end);
  741. end
  742. end
  743. end
  744. %%%
  745. %%% Fix wobble delays in a set of measurements
  746. %%%
  747. function [FIXED] = sphear_fix_wobble(SIG, DELAY);
  748. sinc_size = 255;
  749. min_delay = min(DELAY);
  750. DELAY = DELAY - min_delay;
  751. max_delay = max(DELAY);
  752. for m = [1:size(DELAY, 1)]
  753. %% delay = max_delay - DELAY(m);
  754. delay = DELAY(m);
  755. %% if (delay >= 1)
  756. %% i_delay = floor(delay);
  757. %% f_delay = mod(delay, 1.0);
  758. %% printf("m: %d, delay: %f, integer %f, frac %f\n", m, delay, i_delay, f_delay);
  759. %% FIXED(m,:,:) = zeros(size(SIG, 3));
  760. %% FIXED(m,:,[i_delay:end]) = SIG(m,:,[1:end - i_delay])
  761. %% FIXED(m,:,:) = sphear_delay_signal(FIXED(m,:,:), f_delay, 255, 8192);
  762. %% else
  763. printf("m: %d, delay: %f\n", m, delay);
  764. FIXED(m,:,:) = sphear_delay_signal(SIG(m,:,:), delay, 255, 8192);
  765. %% end
  766. end
  767. end
  768. %%%
  769. %%% Do a basic encoder design, static A2B matrix plus FIR filters
  770. %%%
  771. function [BFF A2B FIR W X Y Z U V] = sphear_calculate_design(fref_min, fref_max)
  772. fft_size = 8192;
  773. N_oct = 16;
  774. %% read in data
  775. [AF, FS] = sphear_af_read((1:16));
  776. %% power in reference band
  777. [R] = sphear_signal_power(AF, FS, fref_min, fref_max, fft_size);
  778. %% static A2B matrix
  779. [A2B, PV, COND] = sphear_calculate_a2b(R);
  780. %% generate a B-format signal
  781. [BF] = sphear_af_to_bf(AF, A2B);
  782. %% calculate power of B-format signals
  783. [B, FC, FE] = sphear_signal_power_all(BF, FS, 300, 20000, fref_min, fref_max, N_oct, fft_size);
  784. %% extract the shapes of wxyz[uv] filters
  785. %% average response in cardinal directions
  786. [W X Y Z U V] = sphear_bf_extract_filter_shapes(B, 0);
  787. %% design FIR filters
  788. [FIR IR MPS] = sphear_design_fir_filters(FC, 512, FS, W, X, Y, Z, U, V, 400, 18, -10);
  789. %% re-calculate the full B-format signal with the filters
  790. [BF, BFF] = sphear_af_to_bf(AF, A2B, FIR, fft_size);
  791. %% get the average power in the directions of the lobes for W, XY and UV
  792. %% and normalize the A2B matrix so that ZY and UV have the same peak amplitude
  793. %% as W
  794. [A2B XY_gain UV_gain] = sphear_bf_normalize_gain(A2B, BFF, FS, fref_min, fref_max, fft_size);
  795. %% re-calculate the B-format signal
  796. [BF, BFF] = sphear_af_to_bf(AF, A2B, FIR, fft_size);
  797. end