sphear_calibration_common.m 44 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450
  1. %% -*- mode: octave -*-
  2. %%
  3. %% *SpHEAR Project
  4. %%
  5. %% Common functions for A2B encoder design
  6. %%
  7. %% Copyright 2016-2019, 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. pkg load plot;
  26. %% turn off the pager
  27. more off
  28. %%
  29. %% Read an A-Format signal for specified measurements
  30. %%
  31. global DATA = "../data/";
  32. global CURRENT = "current/";
  33. function [AF, FS] = sphear_af_read(measurements, raw = false);
  34. global DATA;
  35. global CURRENT;
  36. if (raw)
  37. type = "";
  38. else
  39. type = "cal-";
  40. end
  41. if (exist([DATA CURRENT "af/a-format-cal-001-5.wav"]))
  42. %% assume it is an eight capsule microphone
  43. capnames = cellstr(['1'; '2'; '3'; '4'; '5'; '6'; '7'; '8']);
  44. capsules = 8;
  45. else
  46. %% otherwise a tetrahedral microphone
  47. capnames = cellstr(['LF'; 'RF'; 'LB'; 'RB']);
  48. capsules = 4;
  49. end
  50. %% read all measurements
  51. for m = measurements
  52. %% read all capsules
  53. for c = 1:capsules
  54. %% read in calibrated capsule measurement
  55. [A, FS] = wavread([DATA CURRENT "af/a-format-" type sprintf("%03d", m) "-" char(capnames(c)) ".wav"]);
  56. AF(m, c, :) = A;
  57. end
  58. end
  59. %% read any up/down measurements
  60. if (exist([DATA CURRENT "af/a-format-cal-up-001-5.wav"]))
  61. for m = measurements
  62. %% read all capsules
  63. for c = 1:capsules
  64. %% read in calibrated capsule measurement
  65. [A, FS] = wavread([DATA CURRENT "af/a-format-" type sprintf("up-%03d", m) "-" char(capnames(c)) ".wav"]);
  66. AF(m + 16, c, :) = A;
  67. end
  68. end
  69. end
  70. if (exist([DATA CURRENT "af/a-format-cal-down-001-5.wav"]))
  71. for m = measurements
  72. %% read all capsules
  73. for c = 1:capsules
  74. %% read in calibrated capsule measurement
  75. [A, FS] = wavread([DATA CURRENT "af/a-format-" type sprintf("down-%03d", m) "-" char(capnames(c)) ".wav"]);
  76. AF(m + 16 + 16, c, :) = A;
  77. end
  78. end
  79. end
  80. end
  81. function [AF, FS] = sphear_af_read_all(measurements, raw = false);
  82. global DATA;
  83. global CURRENT;
  84. if (raw)
  85. type = "";
  86. else
  87. type = "cal-";
  88. end
  89. %% are there more than 4 capsules?
  90. if (exist([DATA CURRENT "af/a-format-cal-001-5.wav"]))
  91. %% assume it is an eight capsule microphone
  92. capnames = cellstr(['1'; '2'; '3'; '4'; '5'; '6'; '7'; '8']);
  93. capsules = 8;
  94. else
  95. %% otherwise a tetrahedral microphone
  96. capnames = cellstr(['LF'; 'RF'; 'LB'; 'RB']);
  97. capsules = 4;
  98. end
  99. %% read all measurements
  100. for m = measurements
  101. %% read all capsules
  102. for c = 1:capsules
  103. %% read in calibrated capsule measurement
  104. [A, FS] = wavread([DATA CURRENT "af/a-format-" type sprintf("%03d", m) "-" char(capnames(c)) ".wav"]);
  105. AF(m, c, :) = A;
  106. end
  107. end
  108. %% read any up/down measurements
  109. if (exist([DATA CURRENT "af/a-format-up-cal-001-5.wav"]))
  110. for m = measurements
  111. %% read all capsules
  112. for c = 1:capsules
  113. %% read in calibrated capsule measurement
  114. [A, FS] = wavread([DATA CURRENT "af/a-format-up-" type sprintf("%03d", m) "-" char(capnames(c)) ".wav"]);
  115. AF(m + 16, c, :) = A;
  116. end
  117. end
  118. end
  119. if (exist([DATA CURRENT "af/a-format-down-cal-001-5.wav"]))
  120. for m = measurements
  121. %% read all capsules
  122. for c = 1:capsules
  123. %% read in calibrated capsule measurement
  124. [A, FS] = wavread([DATA CURRENT "af/a-format-down-" type sprintf("%03d", m) "-" char(capnames(c)) ".wav"]);
  125. AF(m + 16 + 16, c, :) = A;
  126. end
  127. end
  128. end
  129. end
  130. %%
  131. %% Read set of calibrated measurements
  132. %%
  133. %% path: path to index of measurements (default: ../data/current/)
  134. %%
  135. %% returns:
  136. %% AF: array of calibrated impulse responses
  137. %% AZ: azimuth for each IR in radians
  138. %% EL: elevation for each IR in radians
  139. %%
  140. %%
  141. %% The index.txt file has the following format:
  142. %% m az el file_name
  143. %% m: measurement number
  144. %% az: azimuth of measurement (radians)
  145. %% el: elevation of measurement (radians)
  146. %% file_name: string with file name of measurement relative to the capture
  147. %% directory where the index.txt file is located
  148. function [AF, AZ, EL, FS] = sphear_af_read_calibrated(path);
  149. if (strcmp(path, ""))
  150. path = "../data/current/";
  151. end
  152. %% read measurement index file
  153. [measurements, azims, elevs, files] = textread(strcat(path, "capture/index.txt"), "%d %f %f %s");
  154. %% find number of channels from first raw measurement capture file
  155. [frames, chans] = wavread(strcat(path, "capture/a-format-", sprintf("%.3d.wav", 1)), "size");
  156. %% read all calibrated files
  157. for measurement = 1:size(measurements);
  158. printf("reading measurement %d, az=%f, el=%f, %s\n", measurement, azims(measurement), elevs(measurement), files(measurement){1,1});
  159. for capsule = 1:chans
  160. [A, FS] = wavread(strcat(path, "cal/a-format-", sprintf("%.3d-%d.wav", measurement, capsule)));
  161. AF(measurement, capsule, :) = A;
  162. end
  163. AZ(measurement) = azims(measurement);
  164. EL(measurement) = elevs(measurement);
  165. end
  166. AZ = AZ';
  167. EL = EL';
  168. end
  169. function [AF, FS] = sphear_af_read_capsule(measurements, raw = false);
  170. global DATA;
  171. global CURRENT;
  172. if (raw)
  173. type = "";
  174. else
  175. type = "cal-";
  176. end
  177. if (exist([DATA CURRENT "af/capsule_u-cal-001-1.wav"]))
  178. %% assume it is an eight capsule microphone
  179. capnames = cellstr(['1'; '2'; '3'; '4'; '5'; '6'; '7'; '8']);
  180. capsules = 1;
  181. else
  182. %% otherwise a tetrahedral microphone
  183. capnames = cellstr(['LF'; 'RF'; 'LB'; 'RB']);
  184. capsules = 4;
  185. end
  186. %% read all measurements
  187. for m = measurements
  188. %% read all capsules
  189. for c = 1:capsules
  190. %% read in calibrated capsule measurement
  191. printf("m:%d -> c:%d\n", m, c);
  192. [A, FS] = wavread([DATA CURRENT "af/capsule_u-" type sprintf("%03d", m) "-" char(capnames(c)) ".wav"]);
  193. AF(m, c, :) = A;
  194. end
  195. end
  196. end
  197. %%
  198. %% Read full t-design coordinates
  199. %%
  200. %% path: path to t-design file (default: ../data/current/)
  201. %%
  202. %% returns:
  203. %% AZ: azimuth for each t-design point in radians
  204. %% EL: elevation for each t-design point in radians
  205. %%
  206. %% The index.txt file has the following format:
  207. %% m az el file_name
  208. %% m: measurement number
  209. %% az: azimuth of measurement (radians)
  210. %% el: elevation of measurement (radians)
  211. function [AZ, EL] = sphear_af_read_t_design(path);
  212. if (strcmp(path, ""))
  213. path = "../data/current/";
  214. end
  215. %% read measurement index file
  216. [measurements, azims, elevs] = textread(strcat(path, "capture/t_design.txt"), "%d %f %f");
  217. AZ = azims;
  218. EL = elevs;
  219. end
  220. %%
  221. %% Calculate power for signal in a frequency range
  222. %%
  223. %% cardioid == 1: replace Z with a cardioid pointing at horizontal angle "azim"
  224. %% cardioid == 2: replace Z with a second order lobe pointing at horizontal angle "azim"
  225. function [R] = sphear_signal_power(SIG, FS, fmin, fmax, fft_size, cardioid = 0, azim = 45)
  226. %% indexes in array for frequency bins
  227. imin = round((fft_size/2) * fmin / (FS/2));
  228. imax = round((fft_size/2) * fmax / (FS/2));
  229. measurements = size(SIG)(1);
  230. signals = size(SIG,2);
  231. %% calculate power in frequency range
  232. for m = (1:measurements)
  233. for s = (1:signals)
  234. if (s == 4 && cardioid > 0)
  235. if (cardioid < 2)
  236. %% replace Z with a cardioid
  237. spectrum = fft((SIG(m, 1, :) +
  238. 1.0 * SIG(m, 2, :) * cos(azim/180*pi) +
  239. 1.0 * SIG(m, 3, :) * sin(azim/180*pi)) / 2, fft_size);
  240. else
  241. %% replace Z with a second order lobe
  242. spectrum = fft((SIG(m, 1, :) +
  243. (1.0 * SIG(m, 2, :) * cos(azim/180*pi)) +
  244. (1.0 * SIG(m, 3, :) * sin(azim/180*pi)) +
  245. (1.0 * SIG(m, 7, :) * cos((2 * azim)/180*pi)) +
  246. (1.0 * SIG(m, 8, :) * sin((2 * azim)/180*pi))) / 2.5, fft_size);
  247. end
  248. else
  249. spectrum = fft(SIG(m, s, :), fft_size);
  250. end
  251. %% return total power of signal in frequency range
  252. R(m,s,:) = sqrt(sum(spectrum(imin:imax).*conj(spectrum(imin:imax)))/(imax - imin));
  253. end
  254. end
  255. end
  256. %%
  257. %% Calculate power for capsule in a frequency range
  258. %%
  259. function [R] = sphear_capsule_power(SIG, FS, fmin, fmax, fft_size)
  260. %% indexes in array for frequency bins
  261. imin = round((fft_size/2) * fmin / (FS/2));
  262. imax = round((fft_size/2) * fmax / (FS/2));
  263. measurements = size(SIG)(1);
  264. signals = size(SIG,2);
  265. %% calculate power in frequency range
  266. for m = (1:measurements)
  267. for s = (1:signals)
  268. spectrum = fft(SIG(m, s, :), fft_size);
  269. %% return total power of signal in frequency range
  270. R(m,s,:) = sqrt(sum(spectrum(imin:imax).*conj(spectrum(imin:imax)))/(imax - imin));
  271. end
  272. end
  273. end
  274. %%
  275. %% Calculate A to B matrix from matrix of power measurements
  276. %%
  277. function [A2B PV COND ERROR] = sphear_calculate_a2b(R, M)
  278. if (exist("M"))
  279. [A2B PV ERROR] = A2B_matrix(R, M);
  280. else
  281. [A2B PV ERROR] = A2B_matrix(R);
  282. end
  283. %% normalize matrix
  284. %%
  285. %% normalize first order components of the matrix first
  286. WXYZ_avg = mean(mean(abs(A2B(:,1:3))));
  287. A2B = A2B ./ WXYZ_avg;
  288. if (size(A2B, 2) > 4)
  289. %% S/T/U/V are difference microphones, so at the reference
  290. %% frequency range their signal is much lower than the
  291. %% first order components, so normalize separately
  292. ST_avg = mean(mean(abs(A2B(:,5:6))));
  293. if (ST_avg != 0)
  294. A2B(:,5:6) = A2B(:,5:6) ./ ST_avg;
  295. end
  296. UV_avg = mean(mean(abs(A2B(:,7:8))));
  297. if (UV_avg != 0)
  298. A2B(:,7:8) = A2B(:,7:8) ./ UV_avg;
  299. end
  300. else
  301. ST_avg = 0;
  302. UV_avg = 0;
  303. end
  304. printf("WXYZ avg = %f, ST avg = %f, UV avg = %f\n", WXYZ_avg, ST_avg, UV_avg);
  305. %% return condition number
  306. COND = cond(A2B);
  307. end
  308. %%
  309. %% Render a calibrated capsule impulse response through an A2B matrix and optional FIR filter
  310. %%
  311. %% m: number of measurement to render
  312. %% A2B: A to B matrix
  313. %% FIR: optional FIR filters
  314. %% fft_size: FFT size for filtering
  315. function [BF, BFF] = sphear_af_to_bf_old(AF, A2B, FIR, fft_size);
  316. measurements = size(AF)(1);
  317. BF = zeros(measurements, size(A2B, 2), size(AF, 3));
  318. for m = (1:measurements)
  319. for c = (1:size(AF,2))
  320. bfc(1,:,:) = reshape(AF(m,c,:), 1, []) .* reshape(A2B(c,:), 1, [])';
  321. BF(m,:,:) = BF(m,:,:) .+ bfc(1,:,:);
  322. end
  323. end
  324. if exist("FIR")
  325. if (size(FIR, 1) <= 8)
  326. %% filter the B format signal through the common FIR filters
  327. for m = (1:measurements)
  328. for b = 1:size(FIR, 1)
  329. %% we add trailing zeros to let the filter ring
  330. BFF(m,b,:) = fftfilt(reshape(FIR(b,:), 1, [])', cat(1, reshape(BF(m,b,:), 1, [])', zeros(2*size(FIR(1,:))(2),1))', fft_size);
  331. end
  332. end
  333. else
  334. %% first four components use common filters, U/V use filter arrays
  335. for m = (1:measurements)
  336. for b = 1:4
  337. BFF(m,b,:) = fftfilt(reshape(FIR(b,:), 1, [])', cat(1, reshape(BF(m,b,:), 1, [])', zeros(2*size(FIR(1,:))(2),1))', fft_size);
  338. end
  339. end
  340. capsules = 8;
  341. for m = (1:measurements)
  342. for b = 5:6
  343. for c = 1:capsules
  344. fi = 4 + (b - 5)*capsules + c;
  345. if (c == 1)
  346. BFF(m,b,:) = fftfilt(reshape(FIR(fi,:), 1, [])', cat(1, reshape(BF(m,b,:), 1, [])', zeros(2*size(FIR(1,:))(2),1))', fft_size);
  347. else
  348. 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);
  349. end
  350. end
  351. end
  352. end
  353. end
  354. else
  355. BFF = BF;
  356. end
  357. end
  358. function [BF, BFF] = sphear_af_to_bf(AF, A2B, FIR, fft_size);
  359. measurements = size(AF)(1);
  360. BF = zeros(measurements, size(A2B, 2), size(AF, 3));
  361. for m = (1:measurements)
  362. for c = (1:size(AF,2))
  363. bfc(1,:,:) = reshape(AF(m,c,:), 1, []) .* reshape(A2B(c,:), 1, [])';
  364. BF(m,:,:) = BF(m,:,:) .+ bfc(1,:,:);
  365. end
  366. end
  367. if exist("FIR")
  368. %% filter the B format signal through the common FIR filters
  369. for m = (1:measurements)
  370. for b = 1:size(FIR, 1)
  371. %% we add trailing zeros to let the filter ring
  372. BFF(m,b,:) = fftfilt(reshape(FIR(b,:), 1, [])', cat(1, reshape(BF(m,b,:), 1, [])', zeros(2*size(FIR(1,:))(2),1))', fft_size);
  373. end
  374. end
  375. else
  376. BFF = BF;
  377. end
  378. end
  379. %%
  380. %% Filter BF components with FIR filters
  381. %%
  382. function [BF] = sphear_filter_bf(B, FIR, fft_size);
  383. measurements = size(B, 1);
  384. bf = size(FIR, 1);
  385. for m = (1:measurements)
  386. for b = (1:bf)
  387. BF(m,b,:) = fftfilt(reshape(FIR(b,:), 1, [])', cat(1, reshape(B(m,b,:), 1, [])', zeros(2*size(FIR(1,:))(2),1))', fft_size);
  388. end
  389. end
  390. end
  391. %%
  392. %% Render a calibrated capsule impulse response through UV matrix filters
  393. %%
  394. %% m: number of measurement to render
  395. %% A2B: A to B matrix
  396. %% FIR: optional FIR filters
  397. %% fft_size: FFT size for filtering
  398. function [UV, UVF] = sphear_af_to_bf_new(AF, A2B, FIR, fft_size);
  399. measurements = size(AF)(1);
  400. BF = zeros(measurements, size(A2B, 2), size(AF, 3));
  401. for m = (1:measurements)
  402. for c = (1:size(AF,2))
  403. bfc(1,:,:) = reshape(AF(m,c,:), 1, []) .* reshape(A2B(c,:), 1, [])';
  404. BF(m,:,:) = BF(m,:,:) .+ bfc(1,:,:);
  405. end
  406. end
  407. if exist("FIR")
  408. if (size(FIR, 1) <= 6)
  409. %% filter the B format signal through four or six FIR filters
  410. for m = (1:measurements)
  411. for b = 1:size(FIR, 1)
  412. %% we add trailing zeros to let the filter ring
  413. BFF(m,b,:) = fftfilt(reshape(FIR(b,:), 1, [])', cat(1, reshape(BF(m,b,:), 1, [])', zeros(2*size(FIR(1,:))(2),1))', fft_size);
  414. end
  415. end
  416. else
  417. %% filter the B format signal through single filters for first order
  418. %% and array of filters for second order
  419. end
  420. else
  421. BFF = BF;
  422. end
  423. end
  424. %% Render a capsule signal through an A2B matrix of filters
  425. %%
  426. %% FIR: matrix of FIR filters
  427. %% fft_size: FFT size for filtering
  428. function [BF] = sphear_af_matrix_filter_to_bf(SIG, FIR, fft_size)
  429. measurements = size(SIG, 1);
  430. %% number of capsules (A format signals)
  431. afcount = size(SIG, 2);
  432. %% number of B format components
  433. bfcount = size(FIR, 3);
  434. for m = (1:measurements)
  435. for afi = (1:afcount)
  436. for bfi = (1:bfcount)
  437. if (afi == 1)
  438. BF(m,bfi,:) = fftfilt(reshape(FIR(:,afi,bfi), 1, [])', cat(1, reshape(SIG(m,afi,:), 1, [])', zeros(2*size(FIR(:,1,1), 1), 1))', fft_size);
  439. else
  440. BF(m,bfi,:) = reshape(BF(m,bfi,:), size(BF, 3), 1)' .+ ...
  441. fftfilt(reshape(FIR(:,afi,bfi), 1, [])', cat(1, reshape(SIG(m,afi,:), 1, [])', zeros(2*size(FIR(:,1,1), 1), 1))', fft_size);
  442. end
  443. end
  444. end
  445. end
  446. end
  447. %%
  448. %% Calculate B format power in logarithmic frequency bands
  449. %%
  450. function [SP, FC, FE] = sphear_signal_power_all(SIG, FS, fmin, fmax, frefmin, frefmax, Noct, fft_size)
  451. %% calculate number of frequency bands spaced Noct
  452. n = floor(log(fmax/fmin)/log(2^(1/Noct)));
  453. %% adjust width of band so they fit exactly in frequency range
  454. N = log(2)/(log(fmax/fmin)/n);
  455. printf("%d frequency bands, %f bands per octave\n", n, N);
  456. %% first center frequency
  457. fcmin = fmin * 2^(1/(N * 2));
  458. %% all center and edge frequencies
  459. FC = fcmin * 2.^((0:n - 1)/N)';
  460. FE = fmin * 2.^((0:n)/N)';
  461. %% normalize power with respect to reference frequency band
  462. %% (measurement,signal,power)
  463. ref = sphear_signal_power(SIG, FS, frefmin, frefmax, fft_size);
  464. norm = mean(ref(:,:));
  465. %% we only normalize with respect to WXYZ, STUV in the frequency range
  466. %% where WXYZ are well behaved has low power (high pass filter due
  467. %% to the difference microphones) so we don't want to average that.
  468. %% If we do that the A2B coefficients are much higher than the ones
  469. %% for WXYZ and we get clipping for signals close to full scale (and
  470. %% then attenuation in the filtering stage after that)
  471. WXYZnorm = mean(ref(:,[1,2,3,4]));
  472. XYavg = mean(WXYZnorm([2,3]));
  473. if (size(SIG, 2) > 4)
  474. %% use XY average for STUV
  475. norm = cat(1, WXYZnorm', [XYavg], [XYavg], [XYavg], [XYavg])';
  476. end
  477. for i = (1:size(FC))
  478. %% (measurement,signal,band,power)
  479. SP(:,:,i) = sphear_signal_power(SIG, FS, FE(i), FE(i + 1), fft_size) ./ norm;
  480. end
  481. end
  482. function [SP, FC, FE, FC1, FE1, FC2, FE2] = sphear_signal_power_all_test(SIG, FS, fmin, fmax, frefmin, frefmax, Noct, fft_size)
  483. %% calculate number of frequency bands spaced Noct
  484. n = floor(log(fmax/fmin)/log(2^(1/Noct)));
  485. n2 = floor(log(fmax/fmin)/log(2^(1/(Noct/2))));
  486. n4 = floor(log(fmax/fmin)/log(2^(1/(Noct/2))));
  487. %% adjust width of band so they fit exactly in frequency range
  488. N = log(2)/(log(fmax/fmin)/n);
  489. N2 = log(2)/(log(fmax/fmin)/n2);
  490. N4 = log(2)/(log(fmax/fmin)/n4);
  491. printf("%d frequency bands, %f bands per octave\n", n, N);
  492. %% first center frequency
  493. fcmin = fmin * 2^(1/(N * 2));
  494. fcmin2 = fmin * 2^(1/(N2 * 2));
  495. fcmin4 = fmin * 2^(1/(N4 * 2));
  496. %% all center and edge frequencies
  497. FC1 = fcmin * 2.^((0:n - 1)/N)';
  498. FE1 = fmin * 2.^((0:n)/N)';
  499. FC2 = fcmin2 * 2.^((0:n2 - 1)/N2)';
  500. FE2 = fmin * 2.^((0:n2)/N2)';
  501. FC4 = fcmin4 * 2.^((0:n4 - 1)/N4)';
  502. FE4 = fmin * 2.^((0:n4)/N4)';
  503. %% merge.....
  504. printf("starting merge\n");
  505. index = 1;
  506. for fi = (1:size(FC2))
  507. fc = FC2(fi);
  508. if (fc < 2000)
  509. FC(index) = FC2(fi);
  510. FE(index) = FE2(fi);
  511. FE(index+1) = FE2(fi+1);
  512. index = index + 1;
  513. end
  514. end
  515. printf("second part of merge\n");
  516. for fi = (1:size(FC1))
  517. fc = FC1(fi);
  518. if (fc >= 2000)
  519. FC(index) = FC1(fi);
  520. FE(index) = FE1(fi);
  521. FE(index+1) = FE1(fi+1);
  522. index = index + 1;
  523. end
  524. end
  525. FC = FC';
  526. FE = FE';
  527. for fc = (1:size(FC))
  528. %% fprintf("fc=%f <%f -> %f>\n", FC(fc), FE(fc), FE(fc+1));
  529. end
  530. %% normalize power with respect to reference frequency band
  531. %% (measurement,signal,power)
  532. ref = sphear_signal_power(SIG, FS, frefmin, frefmax, fft_size);
  533. norm = mean(ref(:,:));
  534. %% we only normalize with respect to WXYZ, STUV in the frequency range
  535. %% where WXYZ are well behaved has low power (high pass filter due
  536. %% to the difference microphones) so we don't want to average that.
  537. %% If we do that the A2B coefficients are much higher than the ones
  538. %% for WXYZ and we get clipping for signals close to full scale (and
  539. %% then attenuation in the filtering stage after that)
  540. WXYZnorm = mean(ref(:,[1,2,3,4]));
  541. XYavg = mean(WXYZnorm([2,3]));
  542. %% use XY average for STUV
  543. norm = cat(1, WXYZnorm', [XYavg], [XYavg], [XYavg], [XYavg])';
  544. for i = (1:size(FC))
  545. %% (measurement,signal,band,power)
  546. SP(:,:,i) = sphear_signal_power(SIG, FS, FE(i), FE(i + 1), fft_size) ./ norm;
  547. end
  548. end
  549. %%
  550. %% Extract average shapes of B format filters
  551. %%
  552. %% old version, use sphear_bf_extract_filter_shape
  553. %% measurements:
  554. %% 0 -> use defaults
  555. %% 1 -> use all measurements
  556. %% 2 -> use cardinal measurements
  557. %% 3 -> use diagonal measurements
  558. function [W X Y Z S T U V] = sphear_bf_extract_filter_shapes(B, measurements)
  559. %% cardinal and diagonal directions for four capsule array
  560. TETRA_CARDINALS = [1, 5, 9, 13];
  561. TETRA_DIAGONALS = [3, 7, 11, 15];
  562. %% cardinal and diagonal directions for eight capsule array
  563. OCTA_CARDINALS = [1, 3, 5, 7, 9, 11, 13, 15];
  564. OCTA_DIAGONALS = [2, 4, 6, 8, 10, 12, 14, 16];
  565. %% best directions for octa WXY
  566. OCTA_FLAT = OCTA_DIAGONALS;
  567. OCTA_BRIGHT = OCTA_CARDINALS;
  568. %% best directions for octa Z
  569. if (size(B, 1) > 32)
  570. OCTA_FLAT_Z = [OCTA_FLAT + 16, OCTA_FLAT + 32];
  571. else
  572. OCTA_FLAT_Z = [OCTA_FLAT + 16];
  573. end
  574. %% best directions for octa UV
  575. U_DIRS = [1, 5, 9, 13];
  576. V_DIRS = [3, 7, 11, 15];
  577. %% best directions for octa ST (up and down layers)
  578. %% hack for only top hemisphere
  579. if (size(B, 1) > 32)
  580. S_DIRS = [U_DIRS + 16, U_DIRS + 32];
  581. T_DIRS = [V_DIRS + 16, V_DIRS + 32];
  582. else
  583. S_DIRS = [U_DIRS + 16];
  584. T_DIRS = [V_DIRS + 16];
  585. end
  586. %% all directions
  587. ALL_DIRS = [1:16];
  588. if (measurements == 0)
  589. %% cardinal directions
  590. if (size(B, 2) > 4)
  591. %% 8 capsules
  592. %% averaging all measurements for W seems to do better
  593. %%W_m = [OCTA_FLAT, OCTA_FLAT + 16, OCTA_FLAT + 32];
  594. %%Y_m = [OCTA_FLAT, OCTA_FLAT + 16, OCTA_FLAT + 32];
  595. %%X_m = [OCTA_FLAT, OCTA_FLAT + 16, OCTA_FLAT + 32];
  596. W_m = OCTA_FLAT;
  597. Y_m = OCTA_FLAT;
  598. X_m = OCTA_FLAT;
  599. if (size(B, 1) > 16)
  600. Z_m = OCTA_FLAT_Z;
  601. else
  602. Z_m = W_m;
  603. end
  604. S_m = S_DIRS;
  605. T_m = T_DIRS;
  606. U_m = U_DIRS;
  607. V_m = V_DIRS;
  608. else
  609. %% four capsules
  610. W_m = TETRA_CARDINALS;
  611. X_m = TETRA_CARDINALS;
  612. Y_m = TETRA_CARDINALS;
  613. Z_m = W_m;
  614. end
  615. else
  616. if (measurements == 1)
  617. %% diagonal directions
  618. if (size(B, 2) > 4)
  619. %% 8 capsules
  620. W_m = OCTA_BRIGHT;
  621. Y_m = OCTA_BRIGHT;
  622. X_m = OCTA_BRIGHT;
  623. Z_m = W_m;
  624. U_m = U_DIRS;
  625. V_m = V_DIRS;
  626. else
  627. %% four capsules
  628. W_m = TETRA_DIAGONALS;
  629. X_m = TETRA_DIAGONALS;
  630. Y_m = TETRA_DIAGONALS;
  631. Z_m = W_m;
  632. end
  633. else
  634. if (measurements == 2)
  635. %% average all measurements for all components
  636. W_m = ALL_DIRS;
  637. X_m = ALL_DIRS;
  638. Y_m = ALL_DIRS;
  639. Z_m = ALL_DIRS;
  640. U_m = ALL_DIRS;
  641. V_m = ALL_DIRS;
  642. end
  643. end
  644. end
  645. %% extract average response shape for W
  646. W = 1.0 ./ reshape(mean(B(W_m,1,:)), size(B(:,:,:))(3), 1);
  647. %% extract average response shape for XYZ
  648. X = 1.0 ./ reshape(mean(B(X_m,2,:)), size(B(:,:,:))(3), 1);
  649. Y = 1.0 ./ reshape(mean(B(Y_m,3,:)), size(B(:,:,:))(3), 1);
  650. Z = 1.0 ./ reshape(mean(B(Z_m,4,:)), size(B(:,:,:))(3), 1);
  651. if (size(B, 2) > 4)
  652. if (size(B, 1) > 16)
  653. %% full 3d measurements
  654. S = 1.0 ./ reshape(mean(B(S_m,5,:)), size(B(:,:,:))(3), 1);
  655. T = 1.0 ./ reshape(mean(B(T_m,6,:)), size(B(:,:,:))(3), 1);
  656. else
  657. %% flat 2d measurements, no S/T filters (ie: flat 1.0 filter)
  658. S = 1.0 ./ ones(size(B, 3), 1);
  659. T = 1.0 ./ ones(size(B, 3), 1);
  660. end
  661. U = 1.0 ./ reshape(mean(B(U_m,7,:)), size(B(:,:,:))(3), 1);
  662. V = 1.0 ./ reshape(mean(B(V_m,8,:)), size(B(:,:,:))(3), 1);
  663. end
  664. end
  665. %%
  666. %% Extract the filter shape of one B format component
  667. %%
  668. %% FC: array of frequency centers of frequency ranges
  669. %% M: projection of measurement points into harmonics
  670. %% B: signals at measurement points
  671. %% H: harmoni to be extracted (w:1, x:2, y:3, z:4, s:5, t:6, u:7, v:8)
  672. %% db: dB's down from peak (1.0) to consider measurement
  673. %% directions: force selection of measurements to consider (for W, mainly)
  674. %% alias_freq: high frequency limit for spatial aliasing
  675. function [FSHAPE] = sphear_bf_extract_filter_shape(FC, M, B, H, db, directions = [], alias_freq = 0)
  676. limit = db2mag(db);
  677. h = 1;
  678. indexes(1) = 0;
  679. if (H == 1)
  680. %% special case for W, it has no entry in M
  681. %% this should be changed in the A2B code...
  682. m = ones(size(M(:,1)), 1);
  683. else
  684. m = M(:,H-1);
  685. end
  686. if (exist("directions") && (numel(directions) > 0))
  687. indexes = directions;
  688. h = numel(directions);
  689. else
  690. %% collect measurements that fit the amplitude criteria
  691. for index = 1:numel(m);
  692. if (abs(m(index)) > limit)
  693. printf("harm:%d select measurement %d\n", H, index);
  694. indexes(h) = index;
  695. h = h + 1;
  696. end
  697. end
  698. end
  699. if (h > 1)
  700. FSHAPE = reshape(mean(abs(m(indexes))) ./ mean(B(indexes,H,:)), [], 1);
  701. else
  702. printf("no %d component above %f dB found\n", H, db);
  703. FSHAPE = ones(size(B,3),1);
  704. end
  705. if (numel(alias_freq) == 1)
  706. %% flatten above alias frequency
  707. alias_slot = 0;
  708. if (alias_freq > 0)
  709. %% find slot
  710. for s = 1:numel(FC)
  711. if (alias_freq <= FC(s) && alias_slot == 0)
  712. alias_slot = s;
  713. end
  714. end
  715. if (alias_slot > 0)
  716. FSHAPE(alias_slot:end,:) = FSHAPE(alias_slot,:);
  717. end
  718. end
  719. else
  720. %% interpolate to the average of an alias transition band
  721. alias_slot(1) = 0;
  722. alias_slot(2) = 0;
  723. %% find slots for band limits
  724. for s = 1:numel(FC)
  725. for ai = 1:2
  726. if (alias_freq(ai) <= FC(s) && alias_slot(ai) == 0)
  727. alias_slot(ai) = s;
  728. end
  729. end
  730. end
  731. %% calculate average on band
  732. band_avg = mean(FSHAPE(alias_slot(1):alias_slot(2),:));
  733. %% size of band in slots
  734. band_s = alias_slot(2) - alias_slot(1) + 1;
  735. %% make an interpolation line
  736. band_int = (1.0:-1/(band_s - 1):0.0)';
  737. %% now replace band with interpolation to average
  738. FSHAPE(alias_slot(1):alias_slot(2),:) = FSHAPE(alias_slot(1):alias_slot(2),:) .* band_int;
  739. FSHAPE(alias_slot(1):alias_slot(2),:) = FSHAPE(alias_slot(1):alias_slot(2),:) .+ (band_avg(:,:) .* (1.0 - band_int));
  740. band_avg
  741. size(FSHAPE,1)
  742. FSHAPE(alias_slot(2)+1:end,:) = FSHAPE(alias_slot(2),:);
  743. end
  744. end
  745. %%
  746. %% Calculate normalization factor for a spherical harmonic
  747. %%
  748. function [NF] = sphear_bf_normalization_factor(FC, M, B, H, db, directions)
  749. limit = db2mag(db);
  750. h = 1;
  751. indexes(1) = 0;
  752. if (H == 1)
  753. %% special case for W, it has no entry in M
  754. %% this should be changed in the A2B code...
  755. m = ones(size(M(:,1)), 1);
  756. else
  757. m = M(:,H-1);
  758. end
  759. if (exist("directions"))
  760. indexes = directions;
  761. h = numel(directions);
  762. else
  763. %% collect measurements that fit the amplitude criteria
  764. for index = 1:numel(m);
  765. if (abs(m(index)) > limit)
  766. printf("harm:%d select measurement %d\n", H, index);
  767. indexes(h) = index;
  768. h = h + 1;
  769. end
  770. end
  771. end
  772. if (h > 1)
  773. NF = mean(reshape(mean(abs(m(indexes)) / B(indexes,H)), [], 1));
  774. else
  775. printf("no %d component above %f dB found\n", H, db);
  776. NF = ones(size(B,3),1);
  777. end
  778. end
  779. %%
  780. %% Extract average shapes of capsule equalization filters
  781. %%
  782. %% (using for now only the horizontal plane measurements)
  783. function [SHAPE] = sphear_extract_capsule_filter_shapes_old(A, neighbors)
  784. %% for each capsule
  785. for c = (1:size(A,2))
  786. %% get indexes of measurements to average for each capsule
  787. mlll = mod(16 - (c * 2) + 0, 16) + 1;
  788. mll = mod(16 - (c * 2) + 1, 16) + 1;
  789. ml = mod(16 - (c * 2) + 2, 16) + 1;
  790. m = mod(16 - (c * 2) + 3, 16) + 1;
  791. mr = mod(16 - (c * 2) + 4, 16) + 1;
  792. mrr = mod(16 - (c * 2) + 5, 16) + 1;
  793. mrrr = mod(16 - (c * 2) + 6, 16) + 1;
  794. for n = (1:5)
  795. mm(n,:) = mod(16 - (c * 2) - n + 2, 16) + 1;
  796. mm(n + 5,:) = mod(16 - (c * 2) + n + 2, 16) + 1;
  797. end
  798. printf("c:%d -> %d %d %d %d %d %d %d\n", c, mlll, mll, ml, m, mr, mrr, mrrr);
  799. if (neighbors > 3)
  800. %% average all directions
  801. SHAPE(c,:) = (1.0 ./ reshape(mean(A(:,c,:)), size(A,3), 1));
  802. else
  803. if (neighbors == 3)
  804. SHAPE(c,:) = (1.0 ./ reshape(mean(A([mlll, mll,ml,m,mr,mrr, mrrr],c,:)), size(A,3), 1));
  805. else
  806. if (neighbors == 2)
  807. SHAPE(c,:) = (1.0 ./ reshape(mean(A([mll,ml,m,mr,mrr],c,:)), size(A,3), 1));
  808. else
  809. SHAPE(c,:) = (1.0 ./ reshape(mean(A([ml,m,mr],c,:)), size(A,3), 1));
  810. end
  811. end
  812. end
  813. end
  814. end
  815. function [D] = distance_between_points(AZ0, EL0, AZ1, EL1)
  816. %% angles expressed in radians, points in unity sphere
  817. D = sqrt(2 + 2*cos(EL1 - EL0) - 2*sin(EL1)*sin(EL0)*cos(AZ1 - AZ0) - 1);
  818. end
  819. function [A] = angle_between_points(AZ0, EL0, AZ1, EL1)
  820. %% angles expressed in radians, points in unity sphere
  821. A = acos(sin(EL1)*sin(EL0) + cos(EL1)*cos(EL0)*cos(AZ1 - AZ0));
  822. end
  823. function [SHAPE PSHAPE] = sphear_find_capsule_filter_shapes(AP, AZ, EL, C, neighbors, min_angle, smooth)
  824. if (!exist("smooth"))
  825. smooth = 0;
  826. end
  827. %% for each capsule
  828. for c = (1:size(C,1))
  829. %% for each measurement
  830. for m = (1:size(AZ,1))
  831. %% find angle between measurement and capsule
  832. angle(m) = angle_between_points(AZ(m), EL(m), C(c,1), C(c,2));
  833. end
  834. %% sort angles in ascending order
  835. [angle_s, i] = sort(angle);
  836. %% select the ones that have a minimum angle with respect to the capsule
  837. %% (this allows us to exclude on-axis measurements)
  838. ii = 1;
  839. for m = 1:size(AZ,1)
  840. if (angle_s(m) >= min_angle)
  841. angle_si(ii) = angle_s(m);
  842. i_i(ii) = i(m);
  843. ii = ii + 1;
  844. end
  845. end
  846. %% now select the neighbors
  847. for n = (1:neighbors)
  848. distance = distance_between_points(AZ(i_i(n)), EL(i_i(n)), C(c,1), C(c,2));
  849. printf("capsule %d: m=%d, a=%f, d=%f (az=%f, elev=%f, corr=%f)\n", c, i_i(n), angle_si(n)/pi*180, distance,
  850. (AZ(i_i(n))/pi*180), (EL(i_i(n))/pi*180), cos(angle_si(n)));
  851. end
  852. if (neighbors == 1)
  853. PSHAPE(c,:) = AP(i_i(1),c,:);
  854. SHAPE(c,:) = 1.0 ./ AP(i_i(1),c,:);
  855. else
  856. %% normalize the shape relative to an ideal cardioid
  857. for n = 1:neighbors
  858. APN(n,:) = AP(i_i(n),c,:) / cos(angle_si(n));
  859. end
  860. PSHAPE(c,:) = mean(APN(1:neighbors,:));
  861. SHAPE(c,:) = 1.0 ./ mean(APN(1:neighbors,:));
  862. %% SHAPE(c,:) = 1.0 ./ reshape(mean(AP(i(1:neighbors),c,:)), [], 1);
  863. %% apply smoothing if requested
  864. if (smooth > 0)
  865. PSHAPE(c,:) = fastsmooth(PSHAPE(c,:),smooth,2,1);
  866. SHAPE(c,:) = fastsmooth(SHAPE(c,:),smooth,2,1);
  867. end
  868. end
  869. end
  870. end
  871. %%
  872. %% Select a set of measurements based on azimuth and elevation constraints
  873. %%
  874. function [SS MS AZS ELS] = sphear_select_azimuth_elevation_range(S, M, AZ, EL, azmin, azmax, elmin, elmax)
  875. q = 1;
  876. for i = 1:numel(AZ)
  877. if ((AZ(i) <= (azmax/180*pi)) && (AZ(i) >= (azmin/180*pi)) && (EL(i) <= (elmax/180*pi)) && EL(i) >= (elmin/180*pi))
  878. %% we are within boundaries
  879. printf("m:%d: found az=%f, el=%f\n", i, AZ(i)/pi*180, EL(i)/pi*180);
  880. SS(q,:,:) = S(i,:,:);
  881. AZS(q,:) = AZ(i);
  882. ELS(q,:) = EL(i);
  883. MS(q,:) = M(i,:);
  884. q = q + 1;
  885. end
  886. end
  887. printf("found %d measurements that qualify\n", numel(AZS));
  888. end
  889. %%
  890. %% Extract equalization filter shape for each capsule
  891. %%
  892. function [SHAPE] = sphear_capsule_filter_shapes(AP, AZ, EL, C)
  893. %% for each capsule
  894. for c = (1:size(C,1))
  895. %%if ((AZ(c) != C(c,1)) || (EL(c) != C(c,2)))
  896. %% printf("error for capsule %d, azimuth or elevation does not match (%f:%f != %f:%f\n", c, AZ(c), EL(c), C(c,1), C(c,2));
  897. %%else
  898. SHAPE(c,:) = 1.0 ./ AP(c,c,:);
  899. %%end
  900. end
  901. end
  902. function [SHAPE] = sphear_side_capsule_filter_shapes(AP, AZ, EL, C)
  903. %% for each capsule
  904. for c = (1:size(C,1))
  905. %%if ((AZ(c) != C(c,1)) || (EL(c) != C(c,2)))
  906. %% printf("error for capsule %d, azimuth or elevation does not match (%f:%f != %f:%f\n", c, AZ(c), EL(c), C(c,1), C(c,2));
  907. %%else
  908. if (c > 2)
  909. cside = mod(c+7,8)-1;
  910. else
  911. cside = c+6;
  912. end
  913. %% printf("c:%d, cside=%f\n", c, cside);
  914. SHAPE(c,:) = 1.0 ./ AP(c,cside,:);
  915. %%end
  916. end
  917. end
  918. %%
  919. %% Filter signals through capsule correction filters
  920. %%
  921. function [AFF] = sphear_filter_capsule_signals(AF, FIR, fft_size);
  922. measurements = size(AF, 1);
  923. capsules = size(AF, 2);
  924. samples = size(AF,3);
  925. AFF = zeros(measurements, capsules, samples);
  926. for m = (1:measurements)
  927. for c = (1:capsules)
  928. %% we add trailing zeros to let the filter ring
  929. 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);
  930. end
  931. end
  932. end
  933. %%
  934. %% Calculate average power for each B format component and normalize A2B matrix
  935. %%
  936. 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)
  937. do_Z = false;
  938. if (size(B,1)/3 > 8)
  939. %% we assume we have three sets of measurements and can do Z averaging
  940. do_Z = true;
  941. end
  942. %% (measurement,signal,band,power)
  943. B_power = sphear_signal_power(B, FS, fmin, fmax, fft_size);
  944. %% W, all measurements
  945. W_avg = mean(B_power(:,1));
  946. %% XY, peak of first order lobes
  947. XY_avg = mean([mean(B_power([1,9], 2)), mean(B_power([5, 13], 3))]);
  948. XY_gain = XY_avg/W_avg;
  949. if (do_Z)
  950. %% Z, +-elev weighted by elevation angle
  951. %% FIXME: we just hardwire the elevation angle for now
  952. if (size(B, 1) > 32)
  953. Z_avg = mean(B_power([17:32,33:48],4)) / sin(43/180*pi);
  954. else
  955. Z_avg = mean(B_power([17:32],4)) / sin(43/180*pi);
  956. end
  957. Z_gain = Z_avg/W_avg;
  958. else
  959. Z_gain = 0.0;
  960. end
  961. if (size(A2B, 2) > 4)
  962. %% UV, peak of second order lobes
  963. U_avg = mean(mean(B_power([1,5,9,13], 7)));
  964. U_gain = U_avg/W_avg;
  965. V_avg = mean(mean(B_power([3,7,11,15], 8)));
  966. V_gain = V_avg/W_avg;
  967. if (abs(mean(mean(A2B(:,[5,6])))) > 0)
  968. %% calculate peak value of s/t at measured elevation
  969. el = 43.0;
  970. s_t_val = 1.0 * sin((2*el)/180*pi);
  971. %% ST, peaks of lobes at +-elevation
  972. if (size(B, 1) > 32)
  973. S_avg = mean([mean(B_power([1,9] + 16, 5)), mean(B_power([1,9] + 32, 5))]);
  974. T_avg = mean([mean(B_power([5,13] + 16, 6)), mean(B_power([5, 13] + 32, 6))]);
  975. else
  976. S_avg = mean([mean(B_power([1,9] + 16, 5))]);
  977. T_avg = mean([mean(B_power([5,13] + 16, 6))]);
  978. end
  979. S_gain = (S_avg/W_avg) * s_t_val;
  980. T_gain = (T_avg/W_avg) * s_t_val;
  981. else
  982. S_gain = 0.0;
  983. T_gain = 0.0;
  984. end
  985. else
  986. S_gain = 0.0;
  987. T_gain = 0.0;
  988. U_gain = 0.0;
  989. V_gain = 0.0;
  990. end
  991. %% normalize the A2B matrix
  992. if (do_Z)
  993. A2B(:,[2,3]) = A2B(:,[2,3]) / XY_gain;
  994. A2B(:,[4]) = A2B(:,[4]) / Z_gain;
  995. else
  996. A2B(:,[2,3,4]) = A2B(:,[2,3,4]) / XY_gain;
  997. end
  998. if (size(A2B, 2) > 4)
  999. if (abs(mean(mean(A2B(:,[5,6])))) > 0)
  1000. printf("correct ST %f %f\n", S_gain, T_gain);
  1001. A2B(:,[5]) = A2B(:,[5]) / S_gain;
  1002. A2B(:,[6]) = A2B(:,[6]) / T_gain;
  1003. end
  1004. A2B(:,[7]) = A2B(:,[7]) / U_gain;
  1005. A2B(:,[8]) = A2B(:,[8]) / V_gain;
  1006. else
  1007. end
  1008. %% we want the W contribution of each capsule to be 1/8,
  1009. %% so scale the whole matrix relative to mean W
  1010. A2B(:,:) = A2B(:,:) / (mean(A2B(:, 1)) * 8);
  1011. %% print gain scaling coefficients
  1012. 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);
  1013. end
  1014. %%
  1015. %% Calculate A2B coefficients in logarithmic frequency bands
  1016. %%
  1017. function [A2BF FC FE] = sphear_calculate_a2b_all(SIG, M, FS, fmin, fmax, Noct, fft_size)
  1018. %% calculate number of frequency bands spaced Noct
  1019. n = floor(log(fmax/fmin)/log(2^(1/Noct)));
  1020. %% adjust width of band so they fit exactly in frequency range
  1021. N = log(2)/(log(fmax/fmin)/n);
  1022. printf("%d frequency bands, %f bands per octave\n", n, N);
  1023. %% first center frequency
  1024. fcmin = fmin * 2^(1/(N * 2));
  1025. %% all center and edge frequencies
  1026. FC = fcmin * 2.^((0:n - 1)/N)';
  1027. FE = fmin * 2.^((0:n)/N)';
  1028. band = 1;
  1029. for i = (1:size(FC))
  1030. %% get power in band
  1031. R = sphear_signal_power(SIG, FS, FE(i), FE(i + 1), fft_size);
  1032. %% calculate a2b matrix in band
  1033. [a2b pv cond] = A2B_matrix(R, M);
  1034. A2BF(band,:,:) = a2b;
  1035. band = band + 1;
  1036. end
  1037. end
  1038. %%
  1039. %% Normalize A2B coefficients in all matrices
  1040. %%
  1041. function [A2BF] = sphear_normalize_a2b_all(A2BF)
  1042. %% normalize WXYZ
  1043. WXYZ_gain = mean(mean(mean(abs(A2BF(:,:,1:4)))));
  1044. A2BF = A2BF ./ WXYZ_gain;
  1045. %% and then ST/UV separately (they have different
  1046. %% gains at the frequency of interest)
  1047. ST_gain = mean(mean(mean(abs(A2BF(:,:,5:6)))));
  1048. UV_gain = mean(mean(mean(abs(A2BF(:,:,7:8)))));
  1049. A2BF(:,:,5:6) = A2BF(:,:,5:6) ./ ST_gain;
  1050. A2BF(:,:,7:8) = A2BF(:,:,7:8) ./ UV_gain;
  1051. printf("WXYZ gain = %f, ST gain = %f, UV gain = %f\n", WXYZ_gain, ST_gain, UV_gain);
  1052. end
  1053. %%
  1054. %% Plot coefficients of A2B matrices as a function of frequency
  1055. %%
  1056. function sphear_plot_a2b_all(FC, A2B, fmin, fmax)
  1057. hold on;
  1058. grid on;
  1059. axis([fmin, fmax]);
  1060. xticks = [300, 500, 1000, 2000, 3000, 5000, 7000, 10000, 15000, 20000];
  1061. set(gca, 'XTick', xticks);
  1062. set(gca, 'XTickLabel', sprintf('%d|', xticks));
  1063. ca=get (gcf, "currentaxes");
  1064. set(ca,"fontweight","bold","linewidth",1.5)
  1065. cmap = jet(size(A2B, 2));
  1066. %% find the max frequency bin
  1067. ifmin = 0;
  1068. ifmax = 0;
  1069. for i = (1:size(A2B, 1))
  1070. if (FC(i) <= fmax)
  1071. ifmax = i;
  1072. end
  1073. end
  1074. ifmax
  1075. FC(ifmax)
  1076. size(A2B, 1)
  1077. %% plot
  1078. semilogx(FC([1:ifmax]), 10*log10(abs(A2B([1:ifmax],:,:))), ".-", "linewidth", 1.5);
  1079. l = legend("C1", "C2", "C3", "C4", "C5", "C6", "C7", "C8");
  1080. %%legend(l, 'location', 'west', 'outside');
  1081. hold off;
  1082. end
  1083. %%%
  1084. %%% Merge 2nd order low frequency filter shape with overall filter shape
  1085. %%%
  1086. function [UC, VC] = sphear_merge_filters(FC, FE, U, V, A2BF, fmax)
  1087. %% find max frequency index
  1088. imax = 0;
  1089. for f = 1:size(FC, 1)
  1090. if (FC(f) < fmax)
  1091. imax = f;
  1092. end
  1093. end
  1094. %% merge filters below the maximum frequency
  1095. for f = 1:size(FC, 1)
  1096. if (FC(f) < fmax)
  1097. for c = 1:size(A2BF, 2)
  1098. %%UC(f,c,:) = U(f) + real(10 * log10(abs(A2BF(f,c,5) / A2BF(imax,c,5))));
  1099. %%VC(f,c,:) = V(f) + real(10 * log10(abs(A2BF(f,c,6) / A2BF(imax,c,6))));
  1100. UC(f,c,:) = U(f) + real(10 * log10(abs(A2BF(imax,c,5) / A2BF(f,c,5))));
  1101. VC(f,c,:) = V(f) + real(10 * log10(A2BF(imax,c,6) / abs(A2BF(f,c,6))));
  1102. end
  1103. else
  1104. for c = 1:size(A2BF, 2)
  1105. UC(f,c,:) = U(f);
  1106. VC(f,c,:) = V(f);
  1107. end
  1108. end
  1109. end
  1110. end
  1111. %%%
  1112. %%% Plot component filters
  1113. %%%
  1114. function sphear_plot_all_filters(FC, UC, fmin, fmax)
  1115. hold on;
  1116. grid on;
  1117. axis([fmin, fmax]);
  1118. xticks = [300, 500, 1000, 2000, 3000, 5000, 7000, 10000, 15000, 20000];
  1119. set(gca, 'XTick', xticks);
  1120. set(gca, 'XTickLabel', sprintf('%d|', xticks));
  1121. ca=get (gcf, "currentaxes");
  1122. set(ca,"fontweight","bold","linewidth",1.5)
  1123. %% find the max frequency bin
  1124. ifmin = 0;
  1125. ifmax = 0;
  1126. for i = (1:size(UC, 1))
  1127. if (FC(i) <= fmax)
  1128. ifmax = i;
  1129. end
  1130. end
  1131. %% plot
  1132. semilogx(FC([1:ifmax]), UC([1:ifmax],:,:), ".-", "linewidth", 1.5);
  1133. l = legend("C1", "C2", "C3", "C4", "C5", "C6", "C7", "C8");
  1134. %%legend(l, 'location', 'west', 'outside');
  1135. hold off;
  1136. end
  1137. %%%
  1138. %%% Plot B format signal for three elevation measurement angles
  1139. %%%
  1140. function sphear_plot_measurements_3d(SIG, FC, FE, FS, N, freq)
  1141. figure;
  1142. hold on;
  1143. axis([1, 16]);
  1144. grid on;
  1145. ca=get (gcf, "currentaxes");
  1146. set(ca,"fontweight","bold","linewidth",1.5)
  1147. %% find the approximate desired frequency band
  1148. for f = [1:size(FC)]
  1149. if (FC(f) <= freq)
  1150. band = f;
  1151. end
  1152. end
  1153. title(["B format power in frequency band (" sprintf("%.0f", FE(band)) " to " sprintf("%.0f", FE(band+1)) " Hz)"]);
  1154. %% and plot the powers
  1155. for i = [1,17,33]
  1156. if (i < 17)
  1157. wstyle = "ko-;W0;";
  1158. color = "r";
  1159. Xname = "X 0elev";
  1160. Yname = "Y 0elev";
  1161. else
  1162. if (i < 33)
  1163. wstyle = "ko--;WU;";
  1164. color = "b";
  1165. Xname = "X +elev";
  1166. Yname = "Y +elev";
  1167. else
  1168. wstyle = "ko:;WD;";
  1169. color = "g";
  1170. Xname = "X -elev";
  1171. Yname = "Y -elev";
  1172. end
  1173. end
  1174. %% W
  1175. plot(SIG((i:i+15),1,band), wstyle, "linewidth", 1.5);
  1176. %% X/Y
  1177. plot(SIG((i:i+15),[2],band), "color", color, sprintf("o-;%s;", Xname), "linewidth", 1.5);
  1178. plot(SIG((i:i+15),[3],band), "color", color, sprintf("o-;%s;", Yname), "linewidth", 1.5);
  1179. %% Z
  1180. pZ = plot(SIG((i:i+15),4,band), "co-;Z;", "linewidth", 1.5);
  1181. end
  1182. legend('location', 'southwestoutside');
  1183. hold off;
  1184. end
  1185. %%%
  1186. %%% Create X matrix for given elevation angles
  1187. %%%
  1188. function [M x y z s t u v az el] = sphear_X_matrix(n, up, down)
  1189. eps = 0.01;
  1190. %% create arrays of angles
  1191. az = (0:360/n:360-eps)';
  1192. %% map zero elevation into spherical harmonic components
  1193. el = 0;
  1194. for i = 1:size(az)
  1195. x(i) = cos(az(i)/180*pi) * cos(el/180*pi);
  1196. y(i) = sin(az(i)/180*pi) * cos(el/180*pi);
  1197. z(i) = sin(el/180*pi);
  1198. s(i) = cos(az(i)/180*pi) * sin((2*el)/180*pi);
  1199. t(i) = sin(az(i)/180*pi) * sin((2*el)/180*pi);
  1200. u(i) = cos((2*az(i))/180*pi) * cos(el/180*pi) * cos(el/180*pi);
  1201. v(i) = sin((2*az(i))/180*pi) * cos(el/180*pi) * cos(el/180*pi);
  1202. end
  1203. %% map positive elevation measurements
  1204. el = up;
  1205. for i = 1:size(az)
  1206. x(i + 16) = cos(az(i)/180*pi) * cos(el/180*pi);
  1207. y(i + 16) = sin(az(i)/180*pi) * cos(el/180*pi);
  1208. z(i + 16) = sin(el/180*pi);
  1209. s(i + 16) = cos(az(i)/180*pi) * sin((2*el)/180*pi);
  1210. t(i + 16) = sin(az(i)/180*pi) * sin((2*el)/180*pi);
  1211. u(i + 16) = cos((2*az(i))/180*pi) * cos(el/180*pi) * cos(el/180*pi);
  1212. v(i + 16) = sin((2*az(i))/180*pi) * cos(el/180*pi) * cos(el/180*pi);
  1213. end
  1214. %% map negative elevation measurements
  1215. el = down;
  1216. for i = 1:size(az)
  1217. x(i + 32) = cos(az(i)/180*pi) * cos(el/180*pi);
  1218. y(i + 32) = sin(az(i)/180*pi) * cos(el/180*pi);
  1219. z(i + 32) = sin(el/180*pi);
  1220. s(i + 32) = cos(az(i)/180*pi) * sin((2*el)/180*pi);
  1221. t(i + 32) = sin(az(i)/180*pi) * sin((2*el)/180*pi);
  1222. u(i + 32) = cos((2*az(i))/180*pi) * cos(el/180*pi) * cos(el/180*pi);
  1223. v(i + 32) = sin((2*az(i))/180*pi) * cos(el/180*pi) * cos(el/180*pi);
  1224. end
  1225. %% return big matrix of projections
  1226. M = [x' y' z' s' t' u' v'];
  1227. end
  1228. %%%
  1229. %%% Create X matrix for given azimuth and elevation angles
  1230. %%%
  1231. function [M x y z s t u v] = sphear_az_el_to_X_matrix(az, el, order = 1)
  1232. for i = 1:size(az)
  1233. x(i) = cos(az(i)) * cos(el(i));
  1234. y(i) = sin(az(i)) * cos(el(i));
  1235. z(i) = sin(el(i));
  1236. if (order > 1)
  1237. s(i) = cos(az(i)) * sin(2*el(i));
  1238. t(i) = sin(az(i)) * sin(2*el(i));
  1239. u(i) = cos(2*az(i)) * cos(el(i)) * cos(el(i));
  1240. v(i) = sin(2*az(i)) * cos(el(i)) * cos(el(i));
  1241. end
  1242. end
  1243. %% return big matrix of projections
  1244. x = x'; y = y'; z = z';
  1245. if (order > 1)
  1246. s = s'; t = t'; u = u'; v = v';
  1247. end
  1248. if (order > 1)
  1249. M = [x y z s t u v];
  1250. else
  1251. M = [x y z];
  1252. end
  1253. end
  1254. %%%
  1255. %%% Fractional delay
  1256. %%%
  1257. %%% based on:
  1258. %%% http://www.labbookpages.co.uk/audio/beamforming/fractionalDelay.html
  1259. %%%
  1260. %%% Also:
  1261. %%% http://users.spa.aalto.fi/vpv/publications/vesan_vaitos/ch3_pt1_fir.pdf
  1262. %%%
  1263. %%% N = length of sinc function (should be odd)
  1264. function [SINC] = sphear_delayed_sinc(N, delay)
  1265. center = floor(N / 2);
  1266. for t = (1:N)
  1267. x = (t - 1) - delay;
  1268. window = 0.54 - 0.46 * cos(2.0 * pi * (x + 0.5) / N);
  1269. SINC(t) = sinc(x - center) * window;
  1270. end
  1271. end
  1272. function [DSIG FIR] = sphear_delay_signal(SIG, delay, N, fft_size)
  1273. FIR = sphear_delayed_sinc(N, delay);
  1274. for m = [1:size(SIG, 1)]
  1275. for c = [1:size(SIG, 2)]
  1276. %% we add trailing zeros to let the filter ring
  1277. DSIG(m, c, :) = fftfilt(FIR, cat(1, reshape(SIG(m, c, :), 1, [])', zeros(2*size(FIR, 2), 1))', fft_size)(round(N/2):end);
  1278. end
  1279. end
  1280. end
  1281. %%%
  1282. %%% Fix wobble delays in a set of measurements
  1283. %%%
  1284. function [FIXED] = sphear_fix_wobble(SIG, DELAY);
  1285. sinc_size = 1023;
  1286. min_delay = min(DELAY);
  1287. DELAY = DELAY - min_delay;
  1288. max_delay = max(DELAY);
  1289. for m = [1:size(DELAY, 1)]
  1290. %% delay = max_delay - DELAY(m);
  1291. delay = DELAY(m);
  1292. %% if (delay >= 1)
  1293. %% i_delay = floor(delay);
  1294. %% f_delay = mod(delay, 1.0);
  1295. %% printf("m: %d, delay: %f, integer %f, frac %f\n", m, delay, i_delay, f_delay);
  1296. %% FIXED(m,:,:) = zeros(size(SIG, 3));
  1297. %% FIXED(m,:,[i_delay:end]) = SIG(m,:,[1:end - i_delay])
  1298. %% FIXED(m,:,:) = sphear_delay_signal(FIXED(m,:,:), f_delay, sinc_size, 8192);
  1299. %% else
  1300. printf("m: %d, delay: %f\n", m, delay);
  1301. FIXED(m,:,:) = sphear_delay_signal(SIG(m,:,:), delay, sinc_size, 8192);
  1302. %% end
  1303. end
  1304. end
  1305. %%%
  1306. %%% Fast convolution using external fconvolver program
  1307. %%%
  1308. %%% SIG = recorded sweep file path
  1309. %%% SWEEP = inverse sweep file path
  1310. function [IR] = fconvolver(IN, ISWEEP);
  1311. %% make temporary file name from input file
  1312. [dir, infile, ext] = fileparts(IN);
  1313. outfile = strcat(infile, "_out", ext);
  1314. %% write an fconvolver configuration file
  1315. cfile = ".fconvolver.cfg";
  1316. cfg = fopen(cfile, 'w');
  1317. %% /convolver/new <inputs> <outputs> <partition size> <maximum impulse length>
  1318. fprintf(cfg, '/convolver/new 1 1 1024 1000000\n');
  1319. %% /impulse/read <input> <output> <gain> <delay> <offset> <length> <channel> <file>
  1320. fprintf(cfg, '/impulse/read 1 1 1.0 0 0 0 1 %s\n', ISWEEP);
  1321. fclose(cfg);
  1322. %% run convolution
  1323. system(sprintf("fconvolver .fconvolver.cfg %s %s\n", IN, outfile));
  1324. %% remove temporary configuration file
  1325. unlink(cfile);
  1326. %% load and return impulse response
  1327. IR = wavread(outfile);
  1328. unlink(outfile);
  1329. end
  1330. %%%
  1331. %%% Do a basic encoder design, static A2B matrix plus FIR filters
  1332. %%%
  1333. function [BFF A2B FIR W X Y Z U V] = sphear_calculate_design(fref_min, fref_max)
  1334. fft_size = 8192;
  1335. N_oct = 16;
  1336. %% read in data
  1337. [AF, FS] = sphear_af_read((1:16));
  1338. %% power in reference band
  1339. [R] = sphear_signal_power(AF, FS, fref_min, fref_max, fft_size);
  1340. %% static A2B matrix
  1341. [A2B, PV, COND] = sphear_calculate_a2b(R);
  1342. %% generate a B-format signal
  1343. [BF] = sphear_af_to_bf(AF, A2B);
  1344. %% calculate power of B-format signals
  1345. [B, FC, FE] = sphear_signal_power_all(BF, FS, 300, 20000, fref_min, fref_max, N_oct, fft_size);
  1346. %% extract the shapes of wxyz[uv] filters
  1347. %% average response in cardinal directions
  1348. [W X Y Z U V] = sphear_bf_extract_filter_shapes(B, 0);
  1349. %% design FIR filters
  1350. [FIR IR MPS] = sphear_design_fir_filters(FC, 512, FS, W, X, Y, Z, U, V, 400, 18, -10);
  1351. %% re-calculate the full B-format signal with the filters
  1352. [BF, BFF] = sphear_af_to_bf(AF, A2B, FIR, fft_size);
  1353. %% get the average power in the directions of the lobes for W, XY and UV
  1354. %% and normalize the A2B matrix so that ZY and UV have the same peak amplitude
  1355. %% as W
  1356. [A2B XY_gain UV_gain] = sphear_bf_normalize_gain(A2B, BFF, FS, fref_min, fref_max, fft_size);
  1357. %% re-calculate the B-format signal
  1358. [BF, BFF] = sphear_af_to_bf(AF, A2B, FIR, fft_size);
  1359. end