%% Simulate the Diffuse Field Response of Microphone Array % Aaron J. Heller (heller@ai.sri.com) % 28-May-2012, revised 22-Jun-2012, 12-Jan-2013 % tested with MATLAB R2012b and GNU Octave, version 3.2.4 %% Main function for simulator function [dfr, ffr] = mic_array_sim_dfr( M, output_file ) % MIC_ARRAY_SIM_DFR simulate diffuse field response pf mic array M % % M.name ... name of array % M.o ... orientaton of capsules (direction cosines) % M.u ... positions of capsules (in meters) % M.alpha .. directivities of capsules, by order % M.a2b ... the A-to-B matrix % (Batke and Elko call this the mode matrix, \Psi) % see MIC_ARRAY_Tetra and _Velan functions below for examples %% array to be simulated, all array parameteters in structure M if ~exist('M', 'var') M = CalrecMkIV; %%M.a2b = A2B'; end %display(M.name); %% Physical constants and simulation parameters % speed of sound c = 340.29; % meters/sec % number of frequencies for simulation n_freqs = 200; low_freq = 100; high_freq = 20000; % log or linear log_freqs = false; % indexes of free-field responses to write out ffr_save = 1:6; % file for output, set to false for no file if ~exist('output_file', 'var') output_file = ['sim_output_', M.name, '.csv']; end % draw a graph? graph_response = true; %% Integration on the Sphere % use order 20 Lebedev-Laikov quadrature a = LebedevGrid(20); % excitation directions, x = [a.x'; a.y'; a.z']; % number of outputs is number of rows in a2b matrix n_Bout = size(M.a2b,2); % quadrature weights, a.w is for full sphere, normalize to 1, duplicate % for each output qw = a.w(:,ones(1,n_Bout))' / (4*pi); % projections of capsule positions along the excitation directions d = M.u * x; % cosine of angle between capsule orientation and excitation directions cos_theta = (M.o * x); size_cos_theta = size(cos_theta); % for octave % directional gain for order i is the Legendre Polynomial, P_i % evaluated at cos_theta dir_gain = diag(M.alpha(:,1)) * ones(size(cos_theta)); % P_0(x) = 1 for i = 2:size(M.alpha,2) if false P_i = legendre(i-1, cos_theta); P_i = squeeze(P_i(1,:,:)); else % for octave P_i = reshape(legendre(i-1, cos_theta(:)'), [i,size_cos_theta]); P_i = squeeze(P_i(1,:,:)); end dir_gain = dir_gain + diag(M.alpha(:,i)) * P_i; end % allocate array to hold diffuse-field frequency response dfr = zeros(n_freqs, n_Bout); % allocate array to hold free-field frequency response ffr = zeros(n_freqs, n_Bout, numel(ffr_save)); % test frequencies if log_freqs freqs = logspace(log10(low_freq), log10(high_freq), n_freqs); else freqs = linspace(low_freq, high_freq, n_freqs); end i = 1; for k = freqs * (2*pi)/c % k is wavenumber % compute B-format outputs at this frequency B = M.a2b * ( dir_gain .* exp( +1j * k * d ) ); ffr(i,:,:) = B(:,ffr_save); % compute RMS responses over surface of sphere dfr(i,:) = sqrt( sum(B .* conj(B) .* qw, 2) ); i = i + 1; end if output_file dlmwrite(... output_file,... [freqs', ... % column 1 dfr, ... % columns 2 ... n_Bout reshape(abs(ffr), numel(freqs), n_Bout*numel(ffr_save))],... 'precision', '%16g'); end if graph_response % Graph it % dfr(:,1) is W, dfr(:,2) is X createfigure(M, freqs, 20*log10(dfr(:,1:2))); end end %% Generic tetrahedral array function M = MIC_ARRAY_Tetra(radius, alpha1) % mic_array_tetra return parameters for generic tetra microphone % RADIUS is the acoustic radius of the array, typically 10% larger % than physical radius. % ALPHA1 is the gain of the first-order parameter % 0 = omni, % 1/2 = cardioid, % 1 = fig-8 % (3-sqrt(3))/2 = supercardioid (~0.63397) % 3/4 = hypercardoid % % The default values for RADIUS and ALPHA1 give the results shown in % Gerzon, "The Design of Precisely Coincident Microphone Arrays for % Stereo and Surround Sound", 50th AES Convention, London, 1975. M.name = 'Tetra'; % Soundfield mic is four mics mounted on the faces of a tetrahedron. if ~exist('radius', 'var') M.r = 1.47 * 1e-2; % meters else M.r = radius; end % capsule directivities % for first-order mic: omni=[1,0]; cardioid=[0.5,0.5],fig8=[0,1]; if ~exist('alpha1', 'var') alpha1 = 0.5; end M.alpha = [ ... 1-alpha1 alpha1; 1-alpha1 alpha1; 1-alpha1 alpha1; 1-alpha1 alpha1; ]; % capsule orientations, direciton cosines M.o = [ ... -1 1 -1; % back-left-down .... LBD +1 1 1; % front-left-up ..... LFU +1 -1 -1; % front-right-down .. RFD -1 -1 1; % back-right-up ..... RBU ]; % normalize M.o = diag(1./sqrt(diag(M.o * M.o'))) * M.o; % capsule positions for radial array, in meters M.u = M.o * M.r; % a2b matrix, Batke calls this the mode matrix, \Psi M.a2b = [ones(size(M.o,1),1), sign(M.o)]'; end %% Calrec MkIV function M = MIC_ARRAY_CalrecMkIV() % original Calrec capsules 10.5 dB f/b ratio M = MIC_ARRAY_Tetra(1.47e-2, 0.35073087); M.name = 'Calrec_MkIV'; end %% Soundfield MkV function M = MIC_ARRAY_SFRMkV() % new capsules used by Sounfield Research, 9.5 dB f/b ratio M = MIC_ARRAY_Tetra(1.47e-2, 0.33251727); M.name = 'Soundfield_Research_MkV'; end %% Danish Pro Audio DPA-4 function M = MIC_ARRAY_DPA() % DPA-4 array M = MIC_ARRAY_Tetra(2.5e-2, 0.5); M.name = 'DPA-4'; % DPA uses different A format order and alternate tetrahedral config % M.a2b = ??????? (get data from Eric, fill in here ) end %% Umashankar's Velan function M = MIC_ARRAY_Velan() M.name = 'Velan'; % Umashankar's Velan is four TSB-140 cardioids with pairs pointed along % axis with separation 14.3 mm. See % http://www.shapeways.com/model/143678/velan-140-internals.html % http://www.firstpr.com.au/rwi/mics/2009-09-a/2011052109473129207TSB-140A25-GP.jpg % TSB-140 is 14mm diameter x 6.3mm depth. I assume the rings are 1 mm % thick. Acoustic radius is typically 20% larger than physical. M.r = 1.2*(14+2+2*6.3)/2 * 1e-3; % meters % capsule directivities % for first-order mic: omni=[1,0]; cardioid=[0.5,0.5],fig8=[0,1]; M.alpha = [ ... 0.5 0.5; 0.5 0.5; 0.5 0.5; 0.5 0.5; 0.5 0.5; 0.5 0.5 ]; % capsule orientations, direciton cosines % a row for each capsule, columns are x,y,z M.o = [ ... 1 0 0; -1 0 0; 0 1 0; 0 -1 0; 0 0 1; 0 0 -1]; if false % specify in spherical (az, el, r), like this M.o = [... 0, 0, 1; 180, 0, 1; 90, 0, 1; -90, 0, 1; 0, 90, 1; 0,-90, 1 ]; % convert to radians M.o(:,1:2) = M.o(:,1:2)*pi/180; % convert to cartesian [M.o(:,1), M.o(:,2), M.o(:,3)] = ... sph2cart(M.o(:,1),M.o(:,2),M.o(:,3)); end % capsule positions for radial array, in meters M.u = M.o .* M.r; % a2b matrix, Batke calls this the mode matrix, \Psi % a column for each capsule, a row for each array output M.a2b = [ ... 0.2357 0.2357 0.2357 0.2357 0.2357 0.2357; 1.0000 -1.0000 0.0000 0.0000 0.0000 0.0000; 0.0000 0.0000 1.0000 -1.0000 0.0000 0.0000; 0 0 0 0 1.0000 -1.0000]; end %% Plotting function function createfigure(M, X1,YMatrix1) % X1: vector of x data % YMATRIX1: matrix of y data % Auto-generated by MATLAB on 12-Mar-2011 11:02:34 % Create figure figure1 = figure(); % Create axes axes1 = axes('Parent',figure1,'YGrid','on','XGrid','on',... 'XMinorTick','on','YMinorTick','on','FontSize',12); box(axes1,'on'); hold(axes1,'all'); % Create multiple lines using matrix input to plot plot1 = plot(X1,YMatrix1,'Parent',axes1); set(plot1(1),'DisplayName','W'); set(plot1(2),'DisplayName','XYZ'); %set(plot1(3),'DisplayName','Y'); % Create title name_sanitized = strrep(M.name, '_', '\_'); if false title(sprintf(... ['Diffuse Field Response of simulated %s Mic Array' ... '\n(r=%0.3fcm, \\alpha=%0.3f)'], ... name_sanitized, M.r*100, M.alpha),... 'FontWeight','bold',... 'FontSize',12); else title(sprintf(... 'Diffuse Field Response of simulated %s Mic Array', ... name_sanitized), ... 'FontWeight','bold',... 'FontSize',12); end % Create xlabel xlabel('Frequency (Hz x 10^4)', 'FontAngle', 'italic'); % Create ylabel ylabel('Relative Response (dB)', 'FontAngle', 'italic'); % Create legend legend1 = legend(axes1,'show'); set(legend1,'Location','SouthWest'); end %% EMB's Octathingy function M = MIC_ARRAY_Octathingy(radius, alpha1) % mic_array_octathingy return parameters for generic tetra microphone % RADIUS is the acoustic radius of the array, typically 10% larger % than physical radius. % ALPHA1 is the gain of the first-order parameter % 0 = omni, % 1/2 = cardioid, % 1 = fig-8 % (3-sqrt(3))/2 = supercardioid (~0.63397) % 3/4 = hypercardoid % M.name = 'Octathingy'; % The octathingy is an octahedron (capsules at the vertices of a cube), % with the top rotated 45deg, so it can do 2nd-order horizontal, % 1st-order vertical. if ~exist('radius', 'var') M.r = 1.47 * 1e-2; % meters else M.r = radius; end % capsule directivities % for first-order mic: omni=[1,0]; cardioid=[0.5,0.5],fig8=[0,1]; if ~exist('alpha1', 'var') alpha1 = 0.5; end M.alpha = [ ... 1-alpha1 alpha1; 1-alpha1 alpha1; 1-alpha1 alpha1; 1-alpha1 alpha1; 1-alpha1 alpha1; 1-alpha1 alpha1; 1-alpha1 alpha1; 1-alpha1 alpha1 ]; % capsule orientations, direciton cosines M.o = [ ... % bottom ring -1 1 -1; % back-left-down .... LBD 1 1 -1; % front-left-down +1 -1 -1; % front-right-down .. RFD -1 -1 -1; % back-right-down 0 1 1; % left-up +1 0 1; % front-up 0 -1 1; % right-up -1 0 1; % back-up ]; % normalize M.o = diag(1./sqrt(diag(M.o * M.o'))) * M.o; % capsule positions for radial array, in meters M.u = M.o * M.r; % a2b matrix, Batke calls this the mode matrix, \Psi M.a2b = [ones(size(M.o,1),1), sign(M.o)]'; end