mic_array_sim_dfr.m 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385
  1. %% Simulate the Diffuse Field Response of Microphone Array
  2. % Aaron J. Heller (heller@ai.sri.com)
  3. % 28-May-2012, revised 22-Jun-2012, 12-Jan-2013
  4. % tested with MATLAB R2012b and GNU Octave, version 3.2.4
  5. % make sure octave treats this as a script file
  6. 1;
  7. %% Main function for simulator
  8. function [dfr, ffr] = mic_array_sim_dfr( M, output_file )
  9. % MIC_ARRAY_SIM_DFR simulate diffuse field response pf mic array M
  10. %
  11. % M.name ... name of array
  12. % M.o ... orientaton of capsules (direction cosines)
  13. % M.u ... positions of capsules (in meters)
  14. % M.alpha .. directivities of capsules, by order
  15. % M.a2b ... the A-to-B matrix
  16. % (Batke and Elko call this the mode matrix, \Psi)
  17. % see MIC_ARRAY_Tetra and _Velan functions below for examples
  18. %% array to be simulated, all array parameteters in structure M
  19. if ~exist('M', 'var')
  20. M = CalrecMkIV;
  21. %%M.a2b = A2B';
  22. end
  23. %display(M.name);
  24. %% Physical constants and simulation parameters
  25. % speed of sound
  26. c = 340.29; % meters/sec
  27. % number of frequencies for simulation
  28. n_freqs = 200;
  29. low_freq = 100;
  30. high_freq = 20000;
  31. % log or linear
  32. log_freqs = false;
  33. % indexes of free-field responses to write out
  34. ffr_save = 1:6;
  35. % file for output, set to false for no file
  36. if ~exist('output_file', 'var')
  37. output_file = ['sim_output_', M.name, '.csv'];
  38. end
  39. % draw a graph?
  40. graph_response = true;
  41. %% Integration on the Sphere
  42. % use order 20 Lebedev-Laikov quadrature
  43. a = LebedevGrid(20);
  44. % excitation directions,
  45. x = [a.x'; a.y'; a.z'];
  46. % number of outputs is number of rows in a2b matrix
  47. n_Bout = size(M.a2b,2);
  48. % quadrature weights, a.w is for full sphere, normalize to 1, duplicate
  49. % for each output
  50. qw = a.w(:,ones(1,n_Bout))' / (4*pi);
  51. % projections of capsule positions along the excitation directions
  52. d = M.u * x;
  53. % cosine of angle between capsule orientation and excitation directions
  54. cos_theta = (M.o * x);
  55. size_cos_theta = size(cos_theta); % for octave
  56. % directional gain for order i is the Legendre Polynomial, P_i
  57. % evaluated at cos_theta
  58. dir_gain = diag(M.alpha(:,1)) * ones(size(cos_theta)); % P_0(x) = 1
  59. for i = 2:size(M.alpha,2)
  60. if false
  61. P_i = legendre(i-1, cos_theta);
  62. P_i = squeeze(P_i(1,:,:));
  63. else % for octave
  64. P_i = reshape(legendre(i-1, cos_theta(:)'), [i,size_cos_theta]);
  65. P_i = squeeze(P_i(1,:,:));
  66. end
  67. dir_gain = dir_gain + diag(M.alpha(:,i)) * P_i;
  68. end
  69. % allocate array to hold diffuse-field frequency response
  70. dfr = zeros(n_freqs, n_Bout);
  71. % allocate array to hold free-field frequency response
  72. ffr = zeros(n_freqs, n_Bout, numel(ffr_save));
  73. % test frequencies
  74. if log_freqs
  75. freqs = logspace(log10(low_freq), log10(high_freq), n_freqs);
  76. else
  77. freqs = linspace(low_freq, high_freq, n_freqs);
  78. end
  79. i = 1;
  80. for k = freqs * (2*pi)/c % k is wavenumber
  81. % compute B-format outputs at this frequency
  82. B = M.a2b * ( dir_gain .* exp( +1j * k * d ) );
  83. ffr(i,:,:) = B(:,ffr_save);
  84. % compute RMS responses over surface of sphere
  85. dfr(i,:) = sqrt( sum(B .* conj(B) .* qw, 2) );
  86. i = i + 1;
  87. end
  88. if output_file
  89. dlmwrite(...
  90. output_file,...
  91. [freqs', ... % column 1
  92. dfr, ... % columns 2 ... n_Bout
  93. reshape(abs(ffr), numel(freqs), n_Bout*numel(ffr_save))],...
  94. 'precision', '%16g');
  95. end
  96. if graph_response
  97. % Graph it
  98. % dfr(:,1) is W, dfr(:,2) is X
  99. createfigure(M, freqs, 20*log10(dfr(:,1:2)));
  100. end
  101. end
  102. %% Generic tetrahedral array
  103. function M = MIC_ARRAY_Tetra(radius, alpha1)
  104. % mic_array_tetra return parameters for generic tetra microphone
  105. % RADIUS is the acoustic radius of the array, typically 10% larger
  106. % than physical radius.
  107. % ALPHA1 is the gain of the first-order parameter
  108. % 0 = omni,
  109. % 1/2 = cardioid,
  110. % 1 = fig-8
  111. % (3-sqrt(3))/2 = supercardioid (~0.63397)
  112. % 3/4 = hypercardoid
  113. %
  114. % The default values for RADIUS and ALPHA1 give the results shown in
  115. % Gerzon, "The Design of Precisely Coincident Microphone Arrays for
  116. % Stereo and Surround Sound", 50th AES Convention, London, 1975.
  117. M.name = 'Tetra';
  118. % Soundfield mic is four mics mounted on the faces of a tetrahedron.
  119. if ~exist('radius', 'var')
  120. M.r = 1.47 * 1e-2; % meters
  121. else
  122. M.r = radius;
  123. end
  124. % capsule directivities
  125. % for first-order mic: omni=[1,0]; cardioid=[0.5,0.5],fig8=[0,1];
  126. if ~exist('alpha1', 'var')
  127. alpha1 = 0.5;
  128. end
  129. M.alpha = [ ...
  130. 1-alpha1 alpha1;
  131. 1-alpha1 alpha1;
  132. 1-alpha1 alpha1;
  133. 1-alpha1 alpha1; ];
  134. % capsule orientations, direciton cosines
  135. M.o = [ ...
  136. -1 1 -1; % back-left-down .... LBD
  137. +1 1 1; % front-left-up ..... LFU
  138. +1 -1 -1; % front-right-down .. RFD
  139. -1 -1 1; % back-right-up ..... RBU
  140. ];
  141. % normalize
  142. M.o = diag(1./sqrt(diag(M.o * M.o'))) * M.o;
  143. % capsule positions for radial array, in meters
  144. M.u = M.o * M.r;
  145. % a2b matrix, Batke calls this the mode matrix, \Psi
  146. M.a2b = [ones(size(M.o,1),1), sign(M.o)]';
  147. end
  148. %% TinySpHEAR prototype #1
  149. function M = MIC_ARRAY_TinySpHEAR()
  150. %
  151. M = MIC_ARRAY_Tetra(0.96e-2, 0.66);
  152. M.name = 'TinySpHEAR';
  153. end
  154. %% Calrec MkIV
  155. function M = MIC_ARRAY_CalrecMkIV()
  156. % original Calrec capsules 10.5 dB f/b ratio
  157. M = MIC_ARRAY_Tetra(1.47e-2, 0.35073087);
  158. M.name = 'Calrec_MkIV';
  159. end
  160. %% Soundfield MkV
  161. function M = MIC_ARRAY_SFRMkV()
  162. % new capsules used by Sounfield Research, 9.5 dB f/b ratio
  163. M = MIC_ARRAY_Tetra(1.47e-2, 0.33251727);
  164. M.name = 'Soundfield_Research_MkV';
  165. end
  166. %% Danish Pro Audio DPA-4
  167. function M = MIC_ARRAY_DPA()
  168. % DPA-4 array
  169. M = MIC_ARRAY_Tetra(2.5e-2, 0.5);
  170. M.name = 'DPA-4';
  171. % DPA uses different A format order and alternate tetrahedral config
  172. % M.a2b = ??????? (get data from Eric, fill in here )
  173. end
  174. %% Umashankar's Velan
  175. function M = MIC_ARRAY_Velan()
  176. M.name = 'Velan';
  177. % Umashankar's Velan is four TSB-140 cardioids with pairs pointed along
  178. % axis with separation 14.3 mm. See
  179. % http://www.shapeways.com/model/143678/velan-140-internals.html
  180. % http://www.firstpr.com.au/rwi/mics/2009-09-a/2011052109473129207TSB-140A25-GP.jpg
  181. % TSB-140 is 14mm diameter x 6.3mm depth. I assume the rings are 1 mm
  182. % thick. Acoustic radius is typically 20% larger than physical.
  183. M.r = 1.2*(14+2+2*6.3)/2 * 1e-3; % meters
  184. % capsule directivities
  185. % for first-order mic: omni=[1,0]; cardioid=[0.5,0.5],fig8=[0,1];
  186. M.alpha = [ ...
  187. 0.5 0.5;
  188. 0.5 0.5;
  189. 0.5 0.5;
  190. 0.5 0.5;
  191. 0.5 0.5;
  192. 0.5 0.5 ];
  193. % capsule orientations, direciton cosines
  194. % a row for each capsule, columns are x,y,z
  195. M.o = [ ...
  196. 1 0 0;
  197. -1 0 0;
  198. 0 1 0;
  199. 0 -1 0;
  200. 0 0 1;
  201. 0 0 -1];
  202. if false
  203. % specify in spherical (az, el, r), like this
  204. M.o = [...
  205. 0, 0, 1;
  206. 180, 0, 1;
  207. 90, 0, 1;
  208. -90, 0, 1;
  209. 0, 90, 1;
  210. 0,-90, 1 ];
  211. % convert to radians
  212. M.o(:,1:2) = M.o(:,1:2)*pi/180;
  213. % convert to cartesian
  214. [M.o(:,1), M.o(:,2), M.o(:,3)] = ...
  215. sph2cart(M.o(:,1),M.o(:,2),M.o(:,3));
  216. end
  217. % capsule positions for radial array, in meters
  218. M.u = M.o .* M.r;
  219. % a2b matrix, Batke calls this the mode matrix, \Psi
  220. % a column for each capsule, a row for each array output
  221. M.a2b = [ ...
  222. 0.2357 0.2357 0.2357 0.2357 0.2357 0.2357;
  223. 1.0000 -1.0000 0.0000 0.0000 0.0000 0.0000;
  224. 0.0000 0.0000 1.0000 -1.0000 0.0000 0.0000;
  225. 0 0 0 0 1.0000 -1.0000];
  226. end
  227. %% Plotting function
  228. function createfigure(M, X1,YMatrix1)
  229. % X1: vector of x data
  230. % YMATRIX1: matrix of y data
  231. % Auto-generated by MATLAB on 12-Mar-2011 11:02:34
  232. % Create figure
  233. figure1 = figure();
  234. % Create axes
  235. axes1 = axes('Parent',figure1,'YGrid','on','XGrid','on',...
  236. 'XMinorTick','on','YMinorTick','on','FontSize',12);
  237. box(axes1,'on');
  238. hold(axes1,'all');
  239. % Create multiple lines using matrix input to plot
  240. plot1 = plot(X1,YMatrix1,'Parent',axes1);
  241. set(plot1(1),'DisplayName','W');
  242. set(plot1(2),'DisplayName','XYZ');
  243. %set(plot1(3),'DisplayName','Y');
  244. % Create title
  245. name_sanitized = strrep(M.name, '_', '\_');
  246. if false
  247. title(sprintf(...
  248. ['Diffuse Field Response of simulated %s Mic Array' ...
  249. '\n(r=%0.3fcm, \\alpha=%0.3f)'], ...
  250. name_sanitized, M.r*100, M.alpha),...
  251. 'FontWeight','bold',...
  252. 'FontSize',12);
  253. else
  254. title(sprintf(...
  255. 'Diffuse Field Response of simulated %s Mic Array', ...
  256. name_sanitized), ...
  257. 'FontWeight','bold',...
  258. 'FontSize',12);
  259. end
  260. % Create xlabel
  261. xlabel('Frequency (Hz x 10^4)', 'FontAngle', 'italic');
  262. % Create ylabel
  263. ylabel('Relative Response (dB)', 'FontAngle', 'italic');
  264. % Create legend
  265. legend1 = legend(axes1,'show');
  266. set(legend1,'Location','SouthWest');
  267. end
  268. %% EMB's Octathingy
  269. function M = MIC_ARRAY_Octathingy(radius, alpha1)
  270. % mic_array_octathingy return parameters for generic tetra microphone
  271. % RADIUS is the acoustic radius of the array, typically 10% larger
  272. % than physical radius.
  273. % ALPHA1 is the gain of the first-order parameter
  274. % 0 = omni,
  275. % 1/2 = cardioid,
  276. % 1 = fig-8
  277. % (3-sqrt(3))/2 = supercardioid (~0.63397)
  278. % 3/4 = hypercardoid
  279. %
  280. M.name = 'Octathingy';
  281. % The octathingy is an octahedron (capsules at the vertices of a cube),
  282. % with the top rotated 45deg, so it can do 2nd-order horizontal,
  283. % 1st-order vertical.
  284. if ~exist('radius', 'var')
  285. M.r = 1.47 * 1e-2; % meters
  286. else
  287. M.r = radius;
  288. end
  289. % capsule directivities
  290. % for first-order mic: omni=[1,0]; cardioid=[0.5,0.5],fig8=[0,1];
  291. if ~exist('alpha1', 'var')
  292. alpha1 = 0.5;
  293. end
  294. M.alpha = [ ...
  295. 1-alpha1 alpha1;
  296. 1-alpha1 alpha1;
  297. 1-alpha1 alpha1;
  298. 1-alpha1 alpha1;
  299. 1-alpha1 alpha1;
  300. 1-alpha1 alpha1;
  301. 1-alpha1 alpha1;
  302. 1-alpha1 alpha1 ];
  303. % capsule orientations, direciton cosines
  304. M.o = [ ...
  305. % bottom ring
  306. -1 1 -1; % back-left-down .... LBD
  307. 1 1 -1; % front-left-down
  308. +1 -1 -1; % front-right-down .. RFD
  309. -1 -1 -1; % back-right-down
  310. 0 1 1; % left-up
  311. +1 0 1; % front-up
  312. 0 -1 1; % right-up
  313. -1 0 1; % back-up
  314. ];
  315. % normalize
  316. M.o = diag(1./sqrt(diag(M.o * M.o'))) * M.o;
  317. % capsule positions for radial array, in meters
  318. M.u = M.o * M.r;
  319. % a2b matrix, Batke calls this the mode matrix, \Psi
  320. M.a2b = [ones(size(M.o,1),1), sign(M.o)]';
  321. end